Merge from Chromium at DEPS revision 261286
This commit was generated by merge_to_master.py.
Change-Id: I756d37445fd7f470b1689ad81318e715d4244987
diff --git a/Source/devtools/front_end/AuditCategory.js b/Source/devtools/front_end/AuditCategory.js
index ee87afe..f6a0ab1 100644
--- a/Source/devtools/front_end/AuditCategory.js
+++ b/Source/devtools/front_end/AuditCategory.js
@@ -49,12 +49,13 @@
},
/**
+ * @param {!WebInspector.Target} target
* @param {!Array.<!WebInspector.NetworkRequest>} requests
- * @param {function(!WebInspector.AuditRuleResult)} ruleResultCallback
+ * @param {function(!WebInspector.AuditRuleResult)} ruleResultCallback
* @param {function()} categoryDoneCallback
* @param {!WebInspector.Progress} progress
*/
- run: function(requests, ruleResultCallback, categoryDoneCallback, progress)
+ run: function(target, requests, ruleResultCallback, categoryDoneCallback, progress)
{
}
}
diff --git a/Source/devtools/front_end/AuditController.js b/Source/devtools/front_end/AuditController.js
index 281ff83..8f4bbd5 100644
--- a/Source/devtools/front_end/AuditController.js
+++ b/Source/devtools/front_end/AuditController.js
@@ -41,10 +41,11 @@
WebInspector.AuditController.prototype = {
/**
+ * @param {!WebInspector.Target} target
* @param {!Array.<!WebInspector.AuditCategory>} categories
* @param {function(string, !Array.<!WebInspector.AuditCategoryResult>)} resultCallback
*/
- _executeAudit: function(categories, resultCallback)
+ _executeAudit: function(target, categories, resultCallback)
{
this._progress.setTitle(WebInspector.UIString("Running audit"));
@@ -63,7 +64,7 @@
}
var results = [];
- var mainResourceURL = WebInspector.resourceTreeModel.inspectedPageURL();
+ var mainResourceURL = target.resourceTreeModel.inspectedPageURL();
var categoriesDone = 0;
/**
@@ -86,7 +87,7 @@
var category = categories[i];
var result = new WebInspector.AuditCategoryResult(category);
results.push(result);
- category.run(requests, ruleResultReadyCallback.bind(this, result), categoryDoneCallback.bind(this), subprogresses[i]);
+ category.run(target, requests, ruleResultReadyCallback.bind(this, result), categoryDoneCallback.bind(this), subprogresses[i]);
}
},
@@ -103,13 +104,14 @@
},
/**
+ * @param {!WebInspector.Target} target
* @param {!Array.<string>} categoryIds
* @param {!WebInspector.Progress} progress
* @param {boolean} runImmediately
* @param {function()} startedCallback
* @param {function()} finishedCallback
*/
- initiateAudit: function(categoryIds, progress, runImmediately, startedCallback, finishedCallback)
+ initiateAudit: function(target, categoryIds, progress, runImmediately, startedCallback, finishedCallback)
{
if (!categoryIds || !categoryIds.length)
return;
@@ -126,7 +128,7 @@
function startAuditWhenResourcesReady()
{
startedCallback();
- this._executeAudit(categories, this._auditFinishedCallback.bind(this, finishedCallback));
+ this._executeAudit(target, categories, this._auditFinishedCallback.bind(this, finishedCallback));
}
if (runImmediately)
diff --git a/Source/devtools/front_end/AuditLauncherView.js b/Source/devtools/front_end/AuditLauncherView.js
index 57f46a2..8e0ec4f 100644
--- a/Source/devtools/front_end/AuditLauncherView.js
+++ b/Source/devtools/front_end/AuditLauncherView.js
@@ -32,6 +32,7 @@
* @constructor
* @param {!WebInspector.AuditController} auditController
* @extends {WebInspector.VBox}
+ * @implements {WebInspector.TargetManager.Observer}
*/
WebInspector.AuditLauncherView = function(auditController)
{
@@ -66,11 +67,30 @@
var defaultSelectedAuditCategory = {};
defaultSelectedAuditCategory[WebInspector.AuditLauncherView.AllCategoriesKey] = true;
this._selectedCategoriesSetting = WebInspector.settings.createSetting("selectedAuditCategories", defaultSelectedAuditCategory);
+ WebInspector.targetManager.observeTargets(this);
}
WebInspector.AuditLauncherView.AllCategoriesKey = "__AllCategories";
WebInspector.AuditLauncherView.prototype = {
+ /**
+ * @param {!WebInspector.Target} target
+ */
+ targetAdded: function(target) { },
+
+ /**
+ * @param {!WebInspector.Target} target
+ */
+ targetRemoved: function(target) { },
+
+ /**
+ * @param {?WebInspector.Target} target
+ */
+ activeTargetChanged: function(target)
+ {
+ this._target = target;
+ },
+
_resetResourceCount: function()
{
this._loadedResources = 0;
@@ -166,7 +186,7 @@
{
this._displayResourceLoadingProgress = false;
}
- this._auditController.initiateAudit(catIds, this._progressIndicator, this._auditPresentStateElement.checked, onAuditStarted.bind(this), this._setAuditRunning.bind(this, false));
+ this._auditController.initiateAudit(this._target, catIds, this._progressIndicator, this._auditPresentStateElement.checked, onAuditStarted.bind(this), this._setAuditRunning.bind(this, false));
},
_stopAudit: function()
diff --git a/Source/devtools/front_end/AuditRules.js b/Source/devtools/front_end/AuditRules.js
index 64d629d..1e863bb 100644
--- a/Source/devtools/front_end/AuditRules.js
+++ b/Source/devtools/front_end/AuditRules.js
@@ -80,12 +80,13 @@
WebInspector.AuditRules.GzipRule.prototype = {
/**
+ * @param {!WebInspector.Target} target
* @param {!Array.<!WebInspector.NetworkRequest>} requests
* @param {!WebInspector.AuditRuleResult} result
* @param {function(?WebInspector.AuditRuleResult)} callback
* @param {!WebInspector.Progress} progress
*/
- doRun: function(requests, result, callback, progress)
+ doRun: function(target, requests, result, callback, progress)
{
var totalSavings = 0;
var compressedSize = 0;
@@ -153,12 +154,13 @@
WebInspector.AuditRules.CombineExternalResourcesRule.prototype = {
/**
+ * @param {!WebInspector.Target} target
* @param {!Array.<!WebInspector.NetworkRequest>} requests
* @param {!WebInspector.AuditRuleResult} result
* @param {function(?WebInspector.AuditRuleResult)} callback
* @param {!WebInspector.Progress} progress
*/
- doRun: function(requests, result, callback, progress)
+ doRun: function(target, requests, result, callback, progress)
{
var domainToResourcesMap = WebInspector.AuditRules.getDomainToResourcesMap(requests, [this._type], false);
var penalizedResourceCount = 0;
@@ -220,12 +222,13 @@
WebInspector.AuditRules.MinimizeDnsLookupsRule.prototype = {
/**
+ * @param {!WebInspector.Target} target
* @param {!Array.<!WebInspector.NetworkRequest>} requests
* @param {!WebInspector.AuditRuleResult} result
* @param {function(?WebInspector.AuditRuleResult)} callback
* @param {!WebInspector.Progress} progress
*/
- doRun: function(requests, result, callback, progress)
+ doRun: function(target, requests, result, callback, progress)
{
var summary = result.addChild("");
var domainToResourcesMap = WebInspector.AuditRules.getDomainToResourcesMap(requests, null, false);
@@ -266,12 +269,13 @@
WebInspector.AuditRules.ParallelizeDownloadRule.prototype = {
/**
+ * @param {!WebInspector.Target} target
* @param {!Array.<!WebInspector.NetworkRequest>} requests
* @param {!WebInspector.AuditRuleResult} result
* @param {function(?WebInspector.AuditRuleResult)} callback
* @param {!WebInspector.Progress} progress
*/
- doRun: function(requests, result, callback, progress)
+ doRun: function(target, requests, result, callback, progress)
{
/**
* @param {string} a
@@ -351,12 +355,13 @@
WebInspector.AuditRules.UnusedCssRule.prototype = {
/**
+ * @param {!WebInspector.Target} target
* @param {!Array.<!WebInspector.NetworkRequest>} requests
* @param {!WebInspector.AuditRuleResult} result
* @param {function(?WebInspector.AuditRuleResult)} callback
* @param {!WebInspector.Progress} progress
*/
- doRun: function(requests, result, callback, progress)
+ doRun: function(target, requests, result, callback, progress)
{
/**
* @param {!Array.<!WebInspector.AuditRules.ParsedStyleSheet>} styleSheets
@@ -458,14 +463,14 @@
if (progress.isCanceled())
return;
var effectiveSelector = selectors[i].replace(pseudoSelectorRegexp, "");
- WebInspector.domModel.querySelector(document.id, effectiveSelector, queryCallback.bind(null, i === selectors.length - 1 ? selectorsCallback.bind(null, styleSheets) : null, selectors[i]));
+ target.domModel.querySelector(document.id, effectiveSelector, queryCallback.bind(null, i === selectors.length - 1 ? selectorsCallback.bind(null, styleSheets) : null, selectors[i]));
}
}
- WebInspector.domModel.requestDocument(documentLoaded.bind(null, selectors));
+ target.domModel.requestDocument(documentLoaded.bind(null, selectors));
}
- var styleSheetInfos = WebInspector.cssModel.allStyleSheets();
+ var styleSheetInfos = target.cssModel.allStyleSheets();
if (!styleSheetInfos || !styleSheetInfos.length) {
evalCallback([]);
return;
@@ -564,12 +569,13 @@
WebInspector.AuditRules.CacheControlRule.prototype = {
/**
+ * @param {!WebInspector.Target} target
* @param {!Array.<!WebInspector.NetworkRequest>} requests
* @param {!WebInspector.AuditRuleResult} result
* @param {function(!WebInspector.AuditRuleResult)} callback
* @param {!WebInspector.Progress} progress
*/
- doRun: function(requests, result, callback, progress)
+ doRun: function(target, requests, result, callback, progress)
{
var cacheableAndNonCacheableResources = this._cacheableAndNonCacheableResources(requests);
if (cacheableAndNonCacheableResources[0].length)
@@ -858,12 +864,13 @@
WebInspector.AuditRules.ImageDimensionsRule.prototype = {
/**
+ * @param {!WebInspector.Target} target
* @param {!Array.<!WebInspector.NetworkRequest>} requests
* @param {!WebInspector.AuditRuleResult} result
* @param {function(?WebInspector.AuditRuleResult)} callback
* @param {!WebInspector.Progress} progress
*/
- doRun: function(requests, result, callback, progress)
+ doRun: function(target, requests, result, callback, progress)
{
var urlToNoDimensionCount = {};
@@ -885,7 +892,7 @@
if (progress.isCanceled())
return;
- const node = WebInspector.domModel.nodeForId(imageId);
+ const node = target.domModel.nodeForId(imageId);
var src = node.getAttribute("src");
if (!src.asParsedURL()) {
for (var frameOwnerCandidate = node; frameOwnerCandidate; frameOwnerCandidate = frameOwnerCandidate.parentNode) {
@@ -961,9 +968,9 @@
doneCallback();
for (var i = 0; nodeIds && i < nodeIds.length; ++i) {
- WebInspector.cssModel.getMatchedStylesAsync(nodeIds[i], false, false, matchedCallback);
- WebInspector.cssModel.getInlineStylesAsync(nodeIds[i], inlineCallback);
- WebInspector.cssModel.getComputedStyleAsync(nodeIds[i], imageStylesReady.bind(null, nodeIds[i], targetResult, i === nodeIds.length - 1));
+ target.cssModel.getMatchedStylesAsync(nodeIds[i], false, false, matchedCallback);
+ target.cssModel.getInlineStylesAsync(nodeIds[i], inlineCallback);
+ target.cssModel.getComputedStyleAsync(nodeIds[i], imageStylesReady.bind(null, nodeIds[i], targetResult, i === nodeIds.length - 1));
}
}
@@ -971,12 +978,12 @@
{
if (progress.isCanceled())
return;
- WebInspector.domModel.querySelectorAll(root.id, "img[src]", getStyles);
+ target.domModel.querySelectorAll(root.id, "img[src]", getStyles);
}
if (progress.isCanceled())
return;
- WebInspector.domModel.requestDocument(onDocumentAvailable);
+ target.domModel.requestDocument(onDocumentAvailable);
},
__proto__: WebInspector.AuditRule.prototype
@@ -993,12 +1000,13 @@
WebInspector.AuditRules.CssInHeadRule.prototype = {
/**
+ * @param {!WebInspector.Target} target
* @param {!Array.<!WebInspector.NetworkRequest>} requests
* @param {!WebInspector.AuditRuleResult} result
* @param {function(?WebInspector.AuditRuleResult)} callback
* @param {!WebInspector.Progress} progress
*/
- doRun: function(requests, result, callback, progress)
+ doRun: function(target, requests, result, callback, progress)
{
function evalCallback(evalResult)
{
@@ -1041,7 +1049,7 @@
var urlToViolationsArray = {};
var externalStylesheetHrefs = [];
for (var j = 0; j < externalStylesheetNodeIds.length; ++j) {
- var linkNode = WebInspector.domModel.nodeForId(externalStylesheetNodeIds[j]);
+ var linkNode = target.domModel.nodeForId(externalStylesheetNodeIds[j]);
var completeHref = WebInspector.ParsedURL.completeURL(linkNode.ownerDocument.baseURL, linkNode.getAttribute("href"));
externalStylesheetHrefs.push(completeHref || "<empty>");
}
@@ -1061,7 +1069,7 @@
if (!nodeIds)
return;
- WebInspector.domModel.querySelectorAll(root.id, "body link[rel~='stylesheet'][href]", externalStylesheetsReceived.bind(null, root, nodeIds));
+ target.domModel.querySelectorAll(root.id, "body link[rel~='stylesheet'][href]", externalStylesheetsReceived.bind(null, root, nodeIds));
}
function onDocumentAvailable(root)
@@ -1069,10 +1077,10 @@
if (progress.isCanceled())
return;
- WebInspector.domModel.querySelectorAll(root.id, "body style", inlineStylesReceived.bind(null, root));
+ target.domModel.querySelectorAll(root.id, "body style", inlineStylesReceived.bind(null, root));
}
- WebInspector.domModel.requestDocument(onDocumentAvailable);
+ target.domModel.requestDocument(onDocumentAvailable);
},
__proto__: WebInspector.AuditRule.prototype
@@ -1089,12 +1097,13 @@
WebInspector.AuditRules.StylesScriptsOrderRule.prototype = {
/**
+ * @param {!WebInspector.Target} target
* @param {!Array.<!WebInspector.NetworkRequest>} requests
* @param {!WebInspector.AuditRuleResult} result
* @param {function(?WebInspector.AuditRuleResult)} callback
* @param {!WebInspector.Progress} progress
*/
- doRun: function(requests, result, callback, progress)
+ doRun: function(target, requests, result, callback, progress)
{
function evalCallback(resultValue)
{
@@ -1137,7 +1146,7 @@
if (lateStyleIds.length || cssBeforeInlineCount) {
var lateStyleUrls = [];
for (var i = 0; i < lateStyleIds.length; ++i) {
- var lateStyleNode = WebInspector.domModel.nodeForId(lateStyleIds[i]);
+ var lateStyleNode = target.domModel.nodeForId(lateStyleIds[i]);
var completeHref = WebInspector.ParsedURL.completeURL(lateStyleNode.ownerDocument.baseURL, lateStyleNode.getAttribute("href"));
lateStyleUrls.push(completeHref || "<empty>");
}
@@ -1159,7 +1168,7 @@
if (!nodeIds)
return;
- WebInspector.domModel.querySelectorAll(root.id, "head link[rel~='stylesheet'][href] ~ script:not([src])", cssBeforeInlineReceived.bind(null, nodeIds));
+ target.domModel.querySelectorAll(root.id, "head link[rel~='stylesheet'][href] ~ script:not([src])", cssBeforeInlineReceived.bind(null, nodeIds));
}
/**
@@ -1170,10 +1179,10 @@
if (progress.isCanceled())
return;
- WebInspector.domModel.querySelectorAll(root.id, "head script[src] ~ link[rel~='stylesheet'][href]", lateStylesReceived.bind(null, root));
+ target.domModel.querySelectorAll(root.id, "head script[src] ~ link[rel~='stylesheet'][href]", lateStylesReceived.bind(null, root));
}
- WebInspector.domModel.requestDocument(onDocumentAvailable);
+ target.domModel.requestDocument(onDocumentAvailable);
},
__proto__: WebInspector.AuditRule.prototype
@@ -1190,14 +1199,15 @@
WebInspector.AuditRules.CSSRuleBase.prototype = {
/**
+ * @param {!WebInspector.Target} target
* @param {!Array.<!WebInspector.NetworkRequest>} requests
* @param {!WebInspector.AuditRuleResult} result
* @param {function(?WebInspector.AuditRuleResult)} callback
* @param {!WebInspector.Progress} progress
*/
- doRun: function(requests, result, callback, progress)
+ doRun: function(target, requests, result, callback, progress)
{
- var headers = WebInspector.cssModel.allStyleSheets();
+ var headers = target.cssModel.allStyleSheets();
if (!headers.length) {
callback(null);
@@ -1385,12 +1395,13 @@
WebInspector.AuditRules.CookieRuleBase.prototype = {
/**
+ * @param {!WebInspector.Target} target
* @param {!Array.<!WebInspector.NetworkRequest>} requests
* @param {!WebInspector.AuditRuleResult} result
* @param {function(!WebInspector.AuditRuleResult)} callback
* @param {!WebInspector.Progress} progress
*/
- doRun: function(requests, result, callback, progress)
+ doRun: function(target, requests, result, callback, progress)
{
var self = this;
function resultCallback(receivedCookies) {
diff --git a/Source/devtools/front_end/AuditsPanel.js b/Source/devtools/front_end/AuditsPanel.js
index a11c6ff..785d79c 100644
--- a/Source/devtools/front_end/AuditsPanel.js
+++ b/Source/devtools/front_end/AuditsPanel.js
@@ -214,12 +214,13 @@
/**
* @override
+ * @param {!WebInspector.Target} target
* @param {!Array.<!WebInspector.NetworkRequest>} requests
* @param {function(!WebInspector.AuditRuleResult)} ruleResultCallback
* @param {function()} categoryDoneCallback
* @param {!WebInspector.Progress} progress
*/
- run: function(requests, ruleResultCallback, categoryDoneCallback, progress)
+ run: function(target, requests, ruleResultCallback, categoryDoneCallback, progress)
{
this._ensureInitialized();
var remainingRulesCount = this._rules.length;
@@ -232,7 +233,7 @@
categoryDoneCallback();
}
for (var i = 0; i < this._rules.length; ++i)
- this._rules[i].run(requests, callbackWrapper, progress);
+ this._rules[i].run(target, requests, callbackWrapper, progress);
},
_ensureInitialized: function()
@@ -294,27 +295,29 @@
},
/**
+ * @param {!WebInspector.Target} target
* @param {!Array.<!WebInspector.NetworkRequest>} requests
* @param {function(!WebInspector.AuditRuleResult)} callback
* @param {!WebInspector.Progress} progress
*/
- run: function(requests, callback, progress)
+ run: function(target, requests, callback, progress)
{
if (progress.isCanceled())
return;
var result = new WebInspector.AuditRuleResult(this.displayName);
result.severity = this._severity;
- this.doRun(requests, result, callback, progress);
+ this.doRun(target, requests, result, callback, progress);
},
/**
+ * @param {!WebInspector.Target} target
* @param {!Array.<!WebInspector.NetworkRequest>} requests
* @param {!WebInspector.AuditRuleResult} result
* @param {function(!WebInspector.AuditRuleResult)} callback
* @param {!WebInspector.Progress} progress
*/
- doRun: function(requests, result, callback, progress)
+ doRun: function(target, requests, result, callback, progress)
{
throw new Error("doRun() not implemented");
}
diff --git a/Source/devtools/front_end/BreakpointManager.js b/Source/devtools/front_end/BreakpointManager.js
index 39dc41b..d7ea366 100644
--- a/Source/devtools/front_end/BreakpointManager.js
+++ b/Source/devtools/front_end/BreakpointManager.js
@@ -487,7 +487,7 @@
*/
_addResolvedLocation: function(location)
{
- this._liveLocations.push(this._breakpointManager._debuggerModel.createLiveLocation(location, this._locationUpdated.bind(this, location)));
+ this._liveLocations.push(location.createLiveLocation(this._locationUpdated.bind(this, location)));
},
/**
@@ -614,7 +614,7 @@
this._resetLocations();
for (var i = 0; i < locations.length; ++i) {
- var script = this._breakpointManager._debuggerModel.scriptForId(locations[i].scriptId);
+ var script = locations[i].script();
var uiLocation = script.rawLocationToUILocation(locations[i].lineNumber, locations[i].columnNumber);
if (this._breakpointManager.findBreakpoint(uiLocation.uiSourceCode, uiLocation.lineNumber, uiLocation.columnNumber)) {
// location clash
diff --git a/Source/devtools/front_end/CPUProfileView.js b/Source/devtools/front_end/CPUProfileView.js
index 46ae079..17d7631 100644
--- a/Source/devtools/front_end/CPUProfileView.js
+++ b/Source/devtools/front_end/CPUProfileView.js
@@ -680,7 +680,7 @@
/**
* @param {string} id
- * @param {!DebuggerAgent.Location} scriptLocation
+ * @param {!WebInspector.DebuggerModel.Location} scriptLocation
* @param {string=} title
*/
consoleProfileStarted: function(id, scriptLocation, title)
@@ -695,7 +695,7 @@
/**
* @param {string} protocolId
- * @param {!DebuggerAgent.Location} scriptLocation
+ * @param {!WebInspector.DebuggerModel.Location} scriptLocation
* @param {!ProfilerAgent.CPUProfile} cpuProfile
* @param {string=} title
*/
@@ -715,13 +715,14 @@
/**
* @param {string} type
- * @param {!DebuggerAgent.Location} scriptLocation
+ * @param {!WebInspector.DebuggerModel.Location} scriptLocation
* @param {string} messageText
*/
_addMessageToConsole: function(type, scriptLocation, messageText)
{
- var script = WebInspector.debuggerModel.scriptForId(scriptLocation.scriptId);
+ var script = scriptLocation.script();
var message = new WebInspector.ConsoleMessage(
+ WebInspector.console.target(),
WebInspector.ConsoleMessage.MessageSource.ConsoleAPI,
WebInspector.ConsoleMessage.MessageLevel.Debug,
messageText,
diff --git a/Source/devtools/front_end/CPUProfilerModel.js b/Source/devtools/front_end/CPUProfilerModel.js
index e7cf719..9afa3b6 100644
--- a/Source/devtools/front_end/CPUProfilerModel.js
+++ b/Source/devtools/front_end/CPUProfilerModel.js
@@ -28,11 +28,14 @@
/**
* @constructor
- * @extends {WebInspector.Object}
+ * @extends {WebInspector.TargetAwareObject}
+ * @param {!WebInspector.Target} target
* @implements {ProfilerAgent.Dispatcher}
*/
-WebInspector.CPUProfilerModel = function()
+WebInspector.CPUProfilerModel = function(target)
{
+ WebInspector.TargetAwareObject.call(this, target);
+
/** @type {?WebInspector.CPUProfilerModel.Delegate} */
this._delegate = null;
this._isRecording = false;
@@ -64,7 +67,7 @@
{
// Make sure ProfilesPanel is initialized and CPUProfileType is created.
WebInspector.moduleManager.loadModule("profiles");
- this._delegate.consoleProfileFinished(id, scriptLocation, cpuProfile, title);
+ this._delegate.consoleProfileFinished(id, WebInspector.DebuggerModel.Location.fromPayload(this.target(), scriptLocation), cpuProfile, title);
},
/**
@@ -76,7 +79,7 @@
{
// Make sure ProfilesPanel is initialized and CPUProfileType is created.
WebInspector.moduleManager.loadModule("profiles");
- this._delegate.consoleProfileStarted(id, scriptLocation, title);
+ this._delegate.consoleProfileStarted(id, WebInspector.DebuggerModel.Location.fromPayload(this.target(), scriptLocation), title);
},
/**
@@ -98,7 +101,7 @@
return this._isRecording;
},
- __proto__: WebInspector.Object.prototype
+ __proto__: WebInspector.TargetAwareObject.prototype
}
/** @interface */
@@ -107,14 +110,14 @@
WebInspector.CPUProfilerModel.Delegate.prototype = {
/**
* @param {string} protocolId
- * @param {!DebuggerAgent.Location} scriptLocation
+ * @param {!WebInspector.DebuggerModel.Location} scriptLocation
* @param {string=} title
*/
consoleProfileStarted: function(protocolId, scriptLocation, title) {},
/**
* @param {string} protocolId
- * @param {!DebuggerAgent.Location} scriptLocation
+ * @param {!WebInspector.DebuggerModel.Location} scriptLocation
* @param {!ProfilerAgent.CPUProfile} cpuProfile
* @param {string=} title
*/
diff --git a/Source/devtools/front_end/CSSStyleModel.js b/Source/devtools/front_end/CSSStyleModel.js
index 2b4a096..83ab712 100644
--- a/Source/devtools/front_end/CSSStyleModel.js
+++ b/Source/devtools/front_end/CSSStyleModel.js
@@ -30,33 +30,38 @@
/**
* @constructor
- * @extends {WebInspector.Object}
- * @param {!WebInspector.Workspace} workspace
+ * @extends {WebInspector.TargetAwareObject}
+ * @param {!WebInspector.Target} target
*/
-WebInspector.CSSStyleModel = function(workspace)
+WebInspector.CSSStyleModel = function(target)
{
- this._workspace = workspace;
+ WebInspector.TargetAwareObject.call(this, target);
+ this._domModel = target.domModel;
+ this._agent = target.cssAgent();
this._pendingCommandsMajorState = [];
this._styleLoader = new WebInspector.CSSStyleModel.ComputedStyleLoader(this);
- WebInspector.domModel.addEventListener(WebInspector.DOMModel.Events.UndoRedoRequested, this._undoRedoRequested, this);
- WebInspector.domModel.addEventListener(WebInspector.DOMModel.Events.UndoRedoCompleted, this._undoRedoCompleted, this);
+ this._domModel.addEventListener(WebInspector.DOMModel.Events.UndoRedoRequested, this._undoRedoRequested, this);
+ this._domModel.addEventListener(WebInspector.DOMModel.Events.UndoRedoCompleted, this._undoRedoCompleted, this);
WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.MainFrameCreatedOrNavigated, this._mainFrameCreatedOrNavigated, this);
InspectorBackend.registerCSSDispatcher(new WebInspector.CSSDispatcher(this));
- CSSAgent.enable(this._wasEnabled.bind(this));
+ this._agent.enable(this._wasEnabled.bind(this));
this._resetStyleSheets();
}
+WebInspector.CSSStyleModel.PseudoStatePropertyName = "pseudoState";
+
/**
+ * @param {!WebInspector.CSSStyleModel} cssModel
* @param {!Array.<!CSSAgent.RuleMatch>|undefined} matchArray
*/
-WebInspector.CSSStyleModel.parseRuleMatchArrayPayload = function(matchArray)
+WebInspector.CSSStyleModel.parseRuleMatchArrayPayload = function(cssModel, matchArray)
{
if (!matchArray)
return [];
var result = [];
for (var i = 0; i < matchArray.length; ++i)
- result.push(WebInspector.CSSRule.parsePayload(matchArray[i].rule, matchArray[i].matchingSelectors));
+ result.push(WebInspector.CSSRule.parsePayload(cssModel, matchArray[i].rule, matchArray[i].matchingSelectors));
return result;
}
@@ -99,6 +104,7 @@
* @param {!Array.<!CSSAgent.RuleMatch>=} matchedPayload
* @param {!Array.<!CSSAgent.PseudoIdMatches>=} pseudoPayload
* @param {!Array.<!CSSAgent.InheritedStyleEntry>=} inheritedPayload
+ * @this {WebInspector.CSSStyleModel}
*/
function callback(userCallback, error, matchedPayload, pseudoPayload, inheritedPayload)
{
@@ -109,13 +115,13 @@
}
var result = {};
- result.matchedCSSRules = WebInspector.CSSStyleModel.parseRuleMatchArrayPayload(matchedPayload);
+ result.matchedCSSRules = WebInspector.CSSStyleModel.parseRuleMatchArrayPayload(this, matchedPayload);
result.pseudoElements = [];
if (pseudoPayload) {
for (var i = 0; i < pseudoPayload.length; ++i) {
var entryPayload = pseudoPayload[i];
- result.pseudoElements.push({ pseudoId: entryPayload.pseudoId, rules: WebInspector.CSSStyleModel.parseRuleMatchArrayPayload(entryPayload.matches) });
+ result.pseudoElements.push({ pseudoId: entryPayload.pseudoId, rules: WebInspector.CSSStyleModel.parseRuleMatchArrayPayload(this, entryPayload.matches) });
}
}
@@ -125,9 +131,9 @@
var entryPayload = inheritedPayload[i];
var entry = {};
if (entryPayload.inlineStyle)
- entry.inlineStyle = WebInspector.CSSStyleDeclaration.parsePayload(entryPayload.inlineStyle);
+ entry.inlineStyle = WebInspector.CSSStyleDeclaration.parsePayload(this, entryPayload.inlineStyle);
if (entryPayload.matchedCSSRules)
- entry.matchedCSSRules = WebInspector.CSSStyleModel.parseRuleMatchArrayPayload(entryPayload.matchedCSSRules);
+ entry.matchedCSSRules = WebInspector.CSSStyleModel.parseRuleMatchArrayPayload(this, entryPayload.matchedCSSRules);
result.inherited.push(entry);
}
}
@@ -136,7 +142,7 @@
userCallback(result);
}
- CSSAgent.getMatchedStylesForNode(nodeId, needPseudo, needInherited, callback.bind(null, userCallback));
+ this._agent.getMatchedStylesForNode(nodeId, needPseudo, needInherited, callback.bind(this, userCallback));
},
/**
@@ -161,7 +167,7 @@
else
callback(cssFamilyName, fonts);
}
- CSSAgent.getPlatformFontsForNode(nodeId, platformFontsCallback);
+ this._agent.getPlatformFontsForNode(nodeId, platformFontsCallback);
},
/**
@@ -199,26 +205,43 @@
* @param {?Protocol.Error} error
* @param {?CSSAgent.CSSStyle=} inlinePayload
* @param {?CSSAgent.CSSStyle=} attributesStylePayload
+ * @this {WebInspector.CSSStyleModel}
*/
function callback(userCallback, error, inlinePayload, attributesStylePayload)
{
if (error || !inlinePayload)
userCallback(null, null);
else
- userCallback(WebInspector.CSSStyleDeclaration.parsePayload(inlinePayload), attributesStylePayload ? WebInspector.CSSStyleDeclaration.parsePayload(attributesStylePayload) : null);
+ userCallback(WebInspector.CSSStyleDeclaration.parsePayload(this, inlinePayload), attributesStylePayload ? WebInspector.CSSStyleDeclaration.parsePayload(this, attributesStylePayload) : null);
}
- CSSAgent.getInlineStylesForNode(nodeId, callback.bind(null, userCallback));
+ this._agent.getInlineStylesForNode(nodeId, callback.bind(this, userCallback));
},
/**
- * @param {!DOMAgent.NodeId} nodeId
- * @param {?Array.<string>|undefined} forcedPseudoClasses
- * @param {function()=} userCallback
+ * @param {!WebInspector.DOMNode} node
+ * @param {string} pseudoClass
+ * @param {boolean} enable
+ * @return {boolean}
*/
- forcePseudoState: function(nodeId, forcedPseudoClasses, userCallback)
+ forcePseudoState: function(node, pseudoClass, enable)
{
- CSSAgent.forcePseudoState(nodeId, forcedPseudoClasses || [], userCallback);
+ var pseudoClasses = node.getUserProperty(WebInspector.CSSStyleModel.PseudoStatePropertyName) || [];
+ if (enable) {
+ if (pseudoClasses.indexOf(pseudoClass) >= 0)
+ return false;
+ pseudoClasses.push(pseudoClass);
+ node.setUserProperty(WebInspector.CSSStyleModel.PseudoStatePropertyName, pseudoClasses);
+ } else {
+ if (pseudoClasses.indexOf(pseudoClass) < 0)
+ return false;
+ pseudoClasses.remove(pseudoClass);
+ if (!pseudoClasses.length)
+ node.removeUserProperty(WebInspector.CSSStyleModel.PseudoStatePropertyName);
+ }
+
+ this._agent.forcePseudoState(node.id, pseudoClasses);
+ return true;
},
/**
@@ -246,13 +269,13 @@
failureCallback();
return;
}
- WebInspector.domModel.markUndoableState();
+ this._domModel.markUndoableState();
this._computeMatchingSelectors(rulePayload, nodeId, successCallback, failureCallback);
}
this._pendingCommandsMajorState.push(true);
- CSSAgent.setRuleSelector(ruleId, newSelector, callback.bind(this, nodeId, successCallback, failureCallback, newSelector));
+ this._agent.setRuleSelector(ruleId, newSelector, callback.bind(this, nodeId, successCallback, failureCallback, newSelector));
},
/**
@@ -268,13 +291,13 @@
failureCallback();
return;
}
- var rule = WebInspector.CSSRule.parsePayload(rulePayload);
+ var rule = WebInspector.CSSRule.parsePayload(this, rulePayload);
var matchingSelectors = [];
var allSelectorsBarrier = new CallbackBarrier();
for (var i = 0; i < rule.selectors.length; ++i) {
var selector = rule.selectors[i];
var boundCallback = allSelectorsBarrier.createCallback(selectorQueried.bind(null, i, nodeId, matchingSelectors));
- WebInspector.domModel.querySelectorAll(ownerDocumentId, selector.value, boundCallback);
+ this._domModel.querySelectorAll(ownerDocumentId, selector.value, boundCallback);
}
allSelectorsBarrier.callWhenDone(function() {
rule.matchingSelectors = matchingSelectors;
@@ -306,7 +329,7 @@
addRule: function(styleSheetId, node, selector, successCallback, failureCallback)
{
this._pendingCommandsMajorState.push(true);
- CSSAgent.addRule(styleSheetId, selector, callback.bind(this));
+ this._agent.addRule(styleSheetId, selector, callback.bind(this));
/**
* @param {?Protocol.Error} error
@@ -320,7 +343,7 @@
// Invalid syntax for a selector
failureCallback();
} else {
- WebInspector.domModel.markUndoableState();
+ this._domModel.markUndoableState();
this._computeMatchingSelectors(rulePayload, node.id, successCallback, failureCallback);
}
}
@@ -356,7 +379,7 @@
callback(this._styleSheetIdToHeader[styleSheetId]);
}
- CSSAgent.createStyleSheet(frameId, innerCallback.bind(this));
+ this._agent.createStyleSheet(frameId, innerCallback.bind(this));
},
mediaQueryResultChanged: function()
@@ -387,7 +410,7 @@
*/
_ownerDocumentId: function(nodeId)
{
- var node = WebInspector.domModel.nodeForId(nodeId);
+ var node = this._domModel.nodeForId(nodeId);
if (!node)
return null;
return node.ownerDocument ? node.ownerDocument.id : null;
@@ -415,7 +438,7 @@
_styleSheetAdded: function(header)
{
console.assert(!this._styleSheetIdToHeader[header.styleSheetId]);
- var styleSheetHeader = new WebInspector.CSSStyleSheetHeader(header);
+ var styleSheetHeader = new WebInspector.CSSStyleSheetHeader(this, header);
this._styleSheetIdToHeader[header.styleSheetId] = styleSheetHeader;
var url = styleSheetHeader.resourceURL();
if (!this._styleSheetIdsForURL[url])
@@ -437,6 +460,8 @@
{
var header = this._styleSheetIdToHeader[id];
console.assert(header);
+ if (!header)
+ return;
delete this._styleSheetIdToHeader[id];
var url = header.resourceURL();
var frameIdToStyleSheetIds = this._styleSheetIdsForURL[url];
@@ -498,7 +523,7 @@
{
this._pendingCommandsMajorState.pop();
if (!error && majorChange)
- WebInspector.domModel.markUndoableState();
+ this._domModel.markUndoableState();
if (!error && userCallback)
userCallback(error);
@@ -570,7 +595,7 @@
return uiLocation || null;
},
- __proto__: WebInspector.Object.prototype
+ __proto__: WebInspector.TargetAwareObject.prototype
}
/**
@@ -651,6 +676,8 @@
dispose: function()
{
WebInspector.LiveLocation.prototype.dispose.call(this);
+ if (this._header)
+ this._header._removeLocation(this);
this._model.removeEventListener(WebInspector.CSSStyleModel.Events.StyleSheetAdded, this._styleSheetAdded, this);
this._model.removeEventListener(WebInspector.CSSStyleModel.Events.StyleSheetRemoved, this._styleSheetRemoved, this);
},
@@ -665,23 +692,47 @@
* @param {number} lineNumber
* @param {number=} columnNumber
*/
-WebInspector.CSSLocation = function(url, lineNumber, columnNumber)
+WebInspector.CSSLocation = function(target, url, lineNumber, columnNumber)
{
+ this._cssModel = target.cssModel;
this.url = url;
this.lineNumber = lineNumber;
this.columnNumber = columnNumber || 0;
}
+WebInspector.CSSLocation.prototype = {
+ /**
+ * @param {?CSSAgent.StyleSheetId} styleSheetId
+ * @param {function(!WebInspector.UILocation):(boolean|undefined)} updateDelegate
+ * @return {?WebInspector.LiveLocation}
+ */
+ createLiveLocation: function(styleSheetId, updateDelegate)
+ {
+ var header = styleSheetId ? this._cssModel.styleSheetHeaderForId(styleSheetId) : null;
+ return new WebInspector.CSSStyleModel.LiveLocation(this._cssModel, header, this, updateDelegate);
+ },
+
+ /**
+ * @return {?WebInspector.UILocation}
+ */
+ toUILocation: function()
+ {
+ return this._cssModel.rawLocationToUILocation(this);
+ }
+}
+
/**
* @constructor
+ * @param {!WebInspector.CSSStyleModel} cssModel
* @param {!CSSAgent.CSSStyle} payload
*/
-WebInspector.CSSStyleDeclaration = function(payload)
+WebInspector.CSSStyleDeclaration = function(cssModel, payload)
{
+ this._cssModel = cssModel;
this.id = payload.styleId;
this.width = payload.width;
this.height = payload.height;
- this.range = payload.range;
+ this.range = payload.range ? WebInspector.TextRange.fromObject(payload.range) : null;
this._shorthandValues = WebInspector.CSSStyleDeclaration.buildShorthandValueMap(payload.shorthandEntries);
this._livePropertyMap = {}; // LIVE properties (source-based or style-based) : { name -> CSSProperty }
this._allProperties = []; // ALL properties: [ CSSProperty ]
@@ -726,28 +777,45 @@
}
/**
+ * @param {!WebInspector.CSSStyleModel} cssModel
* @param {!CSSAgent.CSSStyle} payload
* @return {!WebInspector.CSSStyleDeclaration}
*/
-WebInspector.CSSStyleDeclaration.parsePayload = function(payload)
+WebInspector.CSSStyleDeclaration.parsePayload = function(cssModel, payload)
{
- return new WebInspector.CSSStyleDeclaration(payload);
+ return new WebInspector.CSSStyleDeclaration(cssModel, payload);
}
/**
+ * @param {!WebInspector.CSSStyleModel} cssModel
* @param {!Array.<!CSSAgent.CSSComputedStyleProperty>} payload
* @return {!WebInspector.CSSStyleDeclaration}
*/
-WebInspector.CSSStyleDeclaration.parseComputedStylePayload = function(payload)
+WebInspector.CSSStyleDeclaration.parseComputedStylePayload = function(cssModel, payload)
{
var newPayload = /** @type {!CSSAgent.CSSStyle} */ ({ cssProperties: [], shorthandEntries: [], width: "", height: "" });
if (payload)
newPayload.cssProperties = /** @type {!Array.<!CSSAgent.CSSProperty>} */ (payload);
- return new WebInspector.CSSStyleDeclaration(newPayload);
+ return new WebInspector.CSSStyleDeclaration(cssModel, newPayload);
}
WebInspector.CSSStyleDeclaration.prototype = {
+ /**
+ * @param {string} styleSheetId
+ * @param {!WebInspector.TextRange} oldRange
+ * @param {!WebInspector.TextRange} newRange
+ */
+ sourceStyleSheetEdited: function(styleSheetId, oldRange, newRange)
+ {
+ if (!this.id || this.id.styleSheetId !== styleSheetId)
+ return;
+ if (this.range)
+ this.range = this.range.rebaseAfterTextEdit(oldRange, newRange);
+ for (var i = 0; i < this._allProperties.length; ++i)
+ this._allProperties[i].sourceStyleSheetEdited(styleSheetId, oldRange, newRange);
+ },
+
_computeActiveProperties: function()
{
var activeProperties = {};
@@ -872,10 +940,11 @@
/**
* @param {?string} error
* @param {!CSSAgent.CSSStyle} payload
+ * @this {!WebInspector.CSSStyleDeclaration}
*/
function callback(error, payload)
{
- WebInspector.cssModel._pendingCommandsMajorState.pop();
+ this._cssModel._pendingCommandsMajorState.pop();
if (!userCallback)
return;
@@ -883,14 +952,14 @@
console.error(error);
userCallback(null);
} else
- userCallback(WebInspector.CSSStyleDeclaration.parsePayload(payload));
+ userCallback(WebInspector.CSSStyleDeclaration.parsePayload(this._cssModel, payload));
}
if (!this.id)
throw "No style id";
- WebInspector.cssModel._pendingCommandsMajorState.push(true);
- CSSAgent.setPropertyText(this.id, index, name + ": " + value + ";", false, callback);
+ this._cssModel._pendingCommandsMajorState.push(true);
+ this._cssModel._agent.setPropertyText(this.id, index, name + ": " + value + ";", false, callback.bind(this));
},
/**
@@ -901,20 +970,27 @@
appendProperty: function(name, value, userCallback)
{
this.insertPropertyAt(this.allProperties.length, name, value, userCallback);
- },
+ }
}
/**
* @constructor
+ * @param {!WebInspector.CSSStyleModel} cssModel
* @param {!CSSAgent.CSSRule} payload
* @param {!Array.<number>=} matchingSelectors
*/
-WebInspector.CSSRule = function(payload, matchingSelectors)
+WebInspector.CSSRule = function(cssModel, payload, matchingSelectors)
{
+ this._cssModel = cssModel;
this.id = payload.ruleId;
if (matchingSelectors)
this.matchingSelectors = matchingSelectors;
this.selectors = payload.selectorList.selectors;
+ for (var i = 0; i < this.selectors.length; ++i) {
+ var selector = this.selectors[i];
+ if (selector.range)
+ selector.range = WebInspector.TextRange.fromObject(selector.range);
+ }
this.selectorText = this.selectors.select("value").join(", ");
var firstRange = this.selectors[0].range;
@@ -924,34 +1000,58 @@
}
this.sourceURL = payload.sourceURL;
this.origin = payload.origin;
- this.style = WebInspector.CSSStyleDeclaration.parsePayload(payload.style);
+ this.style = WebInspector.CSSStyleDeclaration.parsePayload(this._cssModel, payload.style);
this.style.parentRule = this;
if (payload.media)
- this.media = WebInspector.CSSMedia.parseMediaArrayPayload(payload.media);
+ this.media = WebInspector.CSSMedia.parseMediaArrayPayload(cssModel, payload.media);
this._setRawLocationAndFrameId();
}
/**
+ * @param {!WebInspector.CSSStyleModel} cssModel
* @param {!CSSAgent.CSSRule} payload
* @param {!Array.<number>=} matchingIndices
* @return {!WebInspector.CSSRule}
*/
-WebInspector.CSSRule.parsePayload = function(payload, matchingIndices)
+WebInspector.CSSRule.parsePayload = function(cssModel, payload, matchingIndices)
{
- return new WebInspector.CSSRule(payload, matchingIndices);
+ return new WebInspector.CSSRule(cssModel, payload, matchingIndices);
}
WebInspector.CSSRule.prototype = {
+ /**
+ * @param {string} styleSheetId
+ * @param {!WebInspector.TextRange} oldRange
+ * @param {!WebInspector.TextRange} newRange
+ */
+ sourceStyleSheetEdited: function(styleSheetId, oldRange, newRange)
+ {
+ if (this.id && this.id.styleSheetId === styleSheetId) {
+ if (this.selectorRange)
+ this.selectorRange = this.selectorRange.rebaseAfterTextEdit(oldRange, newRange);
+ for (var i = 0; i < this.selectors.length; ++i) {
+ var selector = this.selectors[i];
+ if (selector.range)
+ selector.range = selector.range.rebaseAfterTextEdit(oldRange, newRange);
+ }
+ }
+ if (this.media) {
+ for (var i = 0; i < this.media.length; ++i)
+ this.media[i].sourceStyleSheetEdited(styleSheetId, oldRange, newRange);
+ }
+ this.style.sourceStyleSheetEdited(styleSheetId, oldRange, newRange);
+ },
+
_setRawLocationAndFrameId: function()
{
if (!this.id)
return;
- var styleSheetHeader = WebInspector.cssModel.styleSheetHeaderForId(this.id.styleSheetId);
+ var styleSheetHeader = this._cssModel.styleSheetHeaderForId(this.id.styleSheetId);
this.frameId = styleSheetHeader.frameId;
var url = styleSheetHeader.resourceURL();
if (!url)
return;
- this.rawLocation = new WebInspector.CSSLocation(url, this.lineNumberInSource(0), this.columnNumberInSource(0));
+ this.rawLocation = new WebInspector.CSSLocation(this._cssModel.target(), url, this.lineNumberInSource(0), this.columnNumberInSource(0));
},
/**
@@ -961,7 +1061,7 @@
{
if (!this.id)
return "";
- var styleSheetHeader = WebInspector.cssModel.styleSheetHeaderForId(this.id.styleSheetId);
+ var styleSheetHeader = this._cssModel.styleSheetHeaderForId(this.id.styleSheetId);
return styleSheetHeader.resourceURL();
},
@@ -974,7 +1074,7 @@
var selector = this.selectors[selectorIndex];
if (!selector || !selector.range)
return 0;
- var styleSheetHeader = WebInspector.cssModel.styleSheetHeaderForId(this.id.styleSheetId);
+ var styleSheetHeader = this._cssModel.styleSheetHeaderForId(this.id.styleSheetId);
return styleSheetHeader.lineNumberInSource(selector.range.startLine);
},
@@ -987,7 +1087,7 @@
var selector = this.selectors[selectorIndex];
if (!selector || !selector.range)
return undefined;
- var styleSheetHeader = WebInspector.cssModel.styleSheetHeaderForId(this.id.styleSheetId);
+ var styleSheetHeader = this._cssModel.styleSheetHeaderForId(this.id.styleSheetId);
console.assert(styleSheetHeader);
return styleSheetHeader.columnNumberInSource(selector.range.startLine, selector.range.startColumn);
},
@@ -1037,7 +1137,7 @@
this.parsedOk = parsedOk;
this.implicit = implicit;
this.text = text;
- this.range = range;
+ this.range = range ? WebInspector.TextRange.fromObject(range) : null;
}
/**
@@ -1060,6 +1160,19 @@
WebInspector.CSSProperty.prototype = {
/**
+ * @param {string} styleSheetId
+ * @param {!WebInspector.TextRange} oldRange
+ * @param {!WebInspector.TextRange} newRange
+ */
+ sourceStyleSheetEdited: function(styleSheetId, oldRange, newRange)
+ {
+ if (!this.ownerStyle.id || this.ownerStyle.id.styleSheetId !== styleSheetId)
+ return;
+ if (this.range)
+ this.range = this.range.rebaseAfterTextEdit(oldRange, newRange);
+ },
+
+ /**
* @param {boolean} active
*/
_setActive: function(active)
@@ -1121,11 +1234,11 @@
*/
function callback(error, stylePayload)
{
- WebInspector.cssModel._pendingCommandsMajorState.pop();
+ this.ownerStyle._cssModel._pendingCommandsMajorState.pop();
if (!error) {
if (majorChange)
- WebInspector.domModel.markUndoableState();
- var style = WebInspector.CSSStyleDeclaration.parsePayload(stylePayload);
+ this.ownerStyle._cssModel._domModel.markUndoableState();
+ var style = WebInspector.CSSStyleDeclaration.parsePayload(this.ownerStyle._cssModel, stylePayload);
var newProperty = style.allProperties[this.index];
if (newProperty && this.disabled && !propertyText.match(/^\s*$/)) {
@@ -1147,8 +1260,9 @@
throw "No owner style id";
// An index past all the properties adds a new property to the style.
- WebInspector.cssModel._pendingCommandsMajorState.push(majorChange);
- CSSAgent.setPropertyText(this.ownerStyle.id, this.index, propertyText, overwrite, callback.bind(this));
+ var cssModel = this.ownerStyle._cssModel;
+ cssModel._pendingCommandsMajorState.push(majorChange);
+ cssModel._agent.setPropertyText(this.ownerStyle.id, this.index, propertyText, overwrite, callback.bind(this));
},
/**
@@ -1199,17 +1313,19 @@
var line = forName ? range.startLine : range.endLine;
// End of range is exclusive, so subtract 1 from the end offset.
var column = forName ? range.startColumn : range.endColumn - (this.text && this.text.endsWith(";") ? 2 : 1);
- var rawLocation = new WebInspector.CSSLocation(url, line, column);
- return WebInspector.cssModel.rawLocationToUILocation(rawLocation);
+ var rawLocation = new WebInspector.CSSLocation(this.ownerStyle._cssModel.target(), url, line, column);
+ return rawLocation.toUILocation();
}
}
/**
* @constructor
+ * @param {!WebInspector.CSSStyleModel} cssModel
* @param {!CSSAgent.CSSMedia} payload
*/
-WebInspector.CSSMedia = function(payload)
+WebInspector.CSSMedia = function(cssModel, payload)
{
+ this._cssModel = cssModel
this.text = payload.text;
this.source = payload.source;
this.sourceURL = payload.sourceURL || "";
@@ -1225,28 +1341,43 @@
};
/**
+ * @param {!WebInspector.CSSStyleModel} cssModel
* @param {!CSSAgent.CSSMedia} payload
* @return {!WebInspector.CSSMedia}
*/
-WebInspector.CSSMedia.parsePayload = function(payload)
+WebInspector.CSSMedia.parsePayload = function(cssModel, payload)
{
- return new WebInspector.CSSMedia(payload);
+ return new WebInspector.CSSMedia(cssModel, payload);
}
/**
+ * @param {!WebInspector.CSSStyleModel} cssModel
* @param {!Array.<!CSSAgent.CSSMedia>} payload
* @return {!Array.<!WebInspector.CSSMedia>}
*/
-WebInspector.CSSMedia.parseMediaArrayPayload = function(payload)
+WebInspector.CSSMedia.parseMediaArrayPayload = function(cssModel, payload)
{
var result = [];
for (var i = 0; i < payload.length; ++i)
- result.push(WebInspector.CSSMedia.parsePayload(payload[i]));
+ result.push(WebInspector.CSSMedia.parsePayload(cssModel, payload[i]));
return result;
}
WebInspector.CSSMedia.prototype = {
/**
+ * @param {string} styleSheetId
+ * @param {!WebInspector.TextRange} oldRange
+ * @param {!WebInspector.TextRange} newRange
+ */
+ sourceStyleSheetEdited: function(styleSheetId, oldRange, newRange)
+ {
+ if (this.parentStyleSheetId !== styleSheetId)
+ return;
+ if (this.range)
+ this.range = this.range.rebaseAfterTextEdit(oldRange, newRange);
+ },
+
+ /**
* @return {number|undefined}
*/
lineNumberInSource: function()
@@ -1277,17 +1408,19 @@
*/
header: function()
{
- return this.parentStyleSheetId ? WebInspector.cssModel.styleSheetHeaderForId(this.parentStyleSheetId) : null;
+ return this.parentStyleSheetId ? this._cssModel.styleSheetHeaderForId(this.parentStyleSheetId) : null;
}
}
/**
* @constructor
* @implements {WebInspector.ContentProvider}
+ * @param {!WebInspector.CSSStyleModel} cssModel
* @param {!CSSAgent.CSSStyleSheetHeader} payload
*/
-WebInspector.CSSStyleSheetHeader = function(payload)
+WebInspector.CSSStyleSheetHeader = function(cssModel, payload)
{
+ this._cssModel = cssModel;
this.id = payload.styleSheetId;
this.frameId = payload.frameId;
this.sourceURL = payload.sourceURL;
@@ -1346,7 +1479,7 @@
rawLocationToUILocation: function(lineNumber, columnNumber)
{
var uiLocation = null;
- var rawLocation = new WebInspector.CSSLocation(this.resourceURL(), lineNumber, columnNumber);
+ var rawLocation = new WebInspector.CSSLocation(this._cssModel.target(), this.resourceURL(), lineNumber, columnNumber);
for (var i = this._sourceMappings.length - 1; !uiLocation && i >= 0; --i)
uiLocation = this._sourceMappings[i].rawLocationToUILocation(rawLocation);
return uiLocation;
@@ -1429,7 +1562,7 @@
*/
requestContent: function(callback)
{
- CSSAgent.getStyleSheetText(this.id, textCallback.bind(this));
+ this._cssModel._agent.getStyleSheetText(this.id, textCallback.bind(this));
/**
* @this {WebInspector.CSSStyleSheetHeader}
@@ -1469,7 +1602,7 @@
newText = this._trimSourceURL(newText);
if (this.hasSourceURL)
newText += "\n/*# sourceURL=" + this.sourceURL + " */";
- CSSAgent.setStyleSheetText(this.id, newText, callback);
+ this._cssModel._agent.setStyleSheetText(this.id, newText, callback);
},
/**
@@ -1548,7 +1681,7 @@
this._nodeIdToCallbackData[nodeId] = [userCallback];
- CSSAgent.getComputedStyleForNode(nodeId, resultCallback.bind(this, nodeId));
+ this._cssModel._agent.getComputedStyleForNode(nodeId, resultCallback.bind(this, nodeId));
/**
* @param {!DOMAgent.NodeId} nodeId
@@ -1558,7 +1691,7 @@
*/
function resultCallback(nodeId, error, computedPayload)
{
- var computedStyle = (error || !computedPayload) ? null : WebInspector.CSSStyleDeclaration.parseComputedStylePayload(computedPayload);
+ var computedStyle = (error || !computedPayload) ? null : WebInspector.CSSStyleDeclaration.parseComputedStylePayload(this._cssModel, computedPayload);
var callbacks = this._nodeIdToCallbackData[nodeId];
// The loader has been reset.
diff --git a/Source/devtools/front_end/CanvasProfileView.js b/Source/devtools/front_end/CanvasProfileView.js
index fbee2ab..0d17f87 100644
--- a/Source/devtools/front_end/CanvasProfileView.js
+++ b/Source/devtools/front_end/CanvasProfileView.js
@@ -550,7 +550,7 @@
var diffLeft = this._popoverAnchorElement.boxInWindow().x - argumentElement.boxInWindow().x;
this._popoverAnchorElement.style.left = this._popoverAnchorElement.offsetLeft - diffLeft + "px";
- showCallback(WebInspector.RemoteObject.fromPayload(result), false, this._popoverAnchorElement);
+ showCallback(WebInspector.runtimeModel.createRemoteObject(result), false, this._popoverAnchorElement);
}
var evalResult = argumentElement.__evalResult;
@@ -1102,7 +1102,7 @@
element.createTextChild("\"");
element.__suppressPopover = (description.length <= maxStringLength && !/[\r\n]/.test(description));
if (!element.__suppressPopover)
- element.__evalResult = WebInspector.RemoteObject.fromPrimitiveValue(description);
+ element.__evalResult = WebInspector.runtimeModel.createRemoteObjectFromPrimitiveValue(description);
} else {
var type = callArgument.subtype || callArgument.type;
if (type) {
@@ -1112,7 +1112,7 @@
}
element.textContent = description;
if (callArgument.remoteObject)
- element.__evalResult = WebInspector.RemoteObject.fromPayload(callArgument.remoteObject);
+ element.__evalResult = WebInspector.runtimeModel.createRemoteObject(callArgument.remoteObject);
}
if (callArgument.resourceId) {
element.classList.add("canvas-formatted-resource");
@@ -1131,7 +1131,7 @@
var element = document.createElement("span");
element.className = "canvas-call-argument canvas-formatted-number";
element.textContent = enumName;
- element.__evalResult = WebInspector.RemoteObject.fromPrimitiveValue(enumValue);
+ element.__evalResult = WebInspector.runtimeModel.createRemoteObjectFromPrimitiveValue(enumValue);
return element;
}
}
diff --git a/Source/devtools/front_end/CodeMirrorTextEditor.js b/Source/devtools/front_end/CodeMirrorTextEditor.js
index 2a5c328..a241bd1 100644
--- a/Source/devtools/front_end/CodeMirrorTextEditor.js
+++ b/Source/devtools/front_end/CodeMirrorTextEditor.js
@@ -88,7 +88,9 @@
"Tab": "defaultTab",
"Shift-Tab": "indentLess",
"Enter": "smartNewlineAndIndent",
- "Ctrl-Space": "autocomplete"
+ "Ctrl-Space": "autocomplete",
+ "Ctrl-D": "selectNextOccurrence",
+ "Esc": "dismissMultipleSelections"
};
CodeMirror.keyMap["devtools-pc"] = {
@@ -142,12 +144,13 @@
this._shouldClearHistory = true;
this._lineSeparator = "\n";
- this._tokenHighlighter = new WebInspector.CodeMirrorTextEditor.TokenHighlighter(this._codeMirror);
+ this._tokenHighlighter = new WebInspector.CodeMirrorTextEditor.TokenHighlighter(this, this._codeMirror);
this._blockIndentController = new WebInspector.CodeMirrorTextEditor.BlockIndentController(this._codeMirror);
this._fixWordMovement = new WebInspector.CodeMirrorTextEditor.FixWordMovement(this._codeMirror);
this._autocompleteController = new WebInspector.CodeMirrorTextEditor.AutocompleteController(this, this._codeMirror);
+ this._selectNextOccurrenceController = new WebInspector.CodeMirrorTextEditor.SelectNextOccurrenceController(this, this._codeMirror);
- this._codeMirror.on("change", this._change.bind(this));
+ this._codeMirror.on("changes", this._changes.bind(this));
this._codeMirror.on("beforeChange", this._beforeChange.bind(this));
this._codeMirror.on("gutterClick", this._gutterClick.bind(this));
this._codeMirror.on("cursorActivity", this._cursorActivity.bind(this));
@@ -188,12 +191,27 @@
WebInspector.CodeMirrorTextEditor.maxHighlightLength = 1000;
+/**
+ * @param {!CodeMirror} codeMirror
+ */
WebInspector.CodeMirrorTextEditor.autocompleteCommand = function(codeMirror)
{
codeMirror._codeMirrorTextEditor._autocompleteController.autocomplete();
}
CodeMirror.commands.autocomplete = WebInspector.CodeMirrorTextEditor.autocompleteCommand;
+/**
+ * @param {!CodeMirror} codeMirror
+ */
+WebInspector.CodeMirrorTextEditor.selectNextOccurrenceCommand = function(codeMirror)
+{
+ codeMirror._codeMirrorTextEditor._selectNextOccurrenceController.selectNextOccurrence();
+}
+CodeMirror.commands.selectNextOccurrence = WebInspector.CodeMirrorTextEditor.selectNextOccurrenceCommand;
+
+/**
+ * @param {!CodeMirror} codeMirror
+ */
CodeMirror.commands.smartNewlineAndIndent = function(codeMirror)
{
codeMirror.operation(innerSmartNewlineAndIndent.bind(null, codeMirror));
@@ -238,6 +256,15 @@
codemirror._codeMirrorTextEditor._autocompleteController.finishAutocomplete();
}
+CodeMirror.commands.dismissMultipleSelections = function(codemirror)
+{
+ if (codemirror.getSelections().length <= 1)
+ return CodeMirror.Pass;
+ var range = codemirror.listSelections()[0];
+ codemirror.setSelection(range.anchor, range.head, {scroll: false});
+ codemirror._codeMirrorTextEditor._revealLine(range.anchor.line);
+}
+
WebInspector.CodeMirrorTextEditor.LongLineModeLineLengthThreshold = 2000;
WebInspector.CodeMirrorTextEditor.MaximumNumberOfWhitespacesPerSingleSpan = 16;
WebInspector.CodeMirrorTextEditor.MaxEditableTextSize = 1024 * 1024 * 10;
@@ -345,7 +372,7 @@
else
this.setSelection(WebInspector.TextRange.createFromLocation(range.startLine, range.startColumn));
} else {
- // Collapse selection to end on search start so that we jump to next occurence on the first enter press.
+ // Collapse selection to end on search start so that we jump to next occurrence on the first enter press.
this.setSelection(this.selection().collapseToEnd());
}
this._tokenHighlighter.highlightSearchResults(regex, range);
@@ -903,19 +930,17 @@
/**
* @param {number} lineNumber
* @param {number} column
- * @param {boolean=} prefixOnly
* @return {?WebInspector.TextRange}
*/
- _wordRangeForCursorPosition: function(lineNumber, column, prefixOnly)
+ _wordRangeForCursorPosition: function(lineNumber, column)
{
var line = this.line(lineNumber);
- if (column === 0 || !WebInspector.TextUtils.isWordChar(line.charAt(column - 1)))
- return null;
- var wordStart = column - 1;
- while (wordStart > 0 && WebInspector.TextUtils.isWordChar(line.charAt(wordStart - 1)))
- --wordStart;
- if (prefixOnly)
- return new WebInspector.TextRange(lineNumber, wordStart, lineNumber, column);
+ var wordStart = column;
+ if (column !== 0 && WebInspector.TextUtils.isWordChar(line.charAt(column - 1))) {
+ wordStart = column - 1;
+ while (wordStart > 0 && WebInspector.TextUtils.isWordChar(line.charAt(wordStart - 1)))
+ --wordStart;
+ }
var wordEnd = column;
while (wordEnd < line.length && WebInspector.TextUtils.isWordChar(line.charAt(wordEnd)))
++wordEnd;
@@ -937,10 +962,12 @@
/**
* @param {!CodeMirror} codeMirror
- * @param {!WebInspector.CodeMirrorTextEditor.ChangeObject} changeObject
+ * @param {!Array.<!WebInspector.CodeMirrorTextEditor.ChangeObject>} changes
*/
- _change: function(codeMirror, changeObject)
+ _changes: function(codeMirror, changes)
{
+ if (!changes.length)
+ return;
// We do not show "scroll beyond end of file" span for one line documents, so we need to check if "document has one line" changed.
var hasOneLine = this._codeMirror.lineCount() === 1;
if (hasOneLine !== this._hasOneLine)
@@ -959,7 +986,8 @@
var linesToUpdate = {};
var singleCharInput = false;
- do {
+ for (var changeIndex = 0; changeIndex < changes.length; ++changeIndex) {
+ var changeObject = changes[changeIndex];
var oldRange = this._toRange(changeObject.from, changeObject.to);
var newRange = oldRange.clone();
var linesAdded = changeObject.text.length;
@@ -983,7 +1011,7 @@
for (var i = newRange.startLine; i <= newRange.endLine; ++i)
linesToUpdate[i] = this.line(i);
}
- } while (changeObject = changeObject.next);
+ }
if (this._dictionary) {
for (var lineNumber in linesToUpdate)
this._addTextToCompletionDictionary(linesToUpdate[lineNumber]);
@@ -1003,13 +1031,17 @@
/**
* @param {!CodeMirror} codeMirror
- * @param {!{head: !CodeMirror.Pos, anchor: !CodeMirror.Pos}} selection
+ * @param {{ranges: !Array.<{head: !CodeMirror.Pos, anchor: !CodeMirror.Pos}>}} selection
*/
_beforeSelectionChange: function(codeMirror, selection)
{
+ this._selectNextOccurrenceController.selectionWillChange();
if (!this._isHandlingMouseDownEvent)
return;
- this._reportJump(this.selection(), this._toRange(selection.anchor, selection.head));
+ if (!selection.ranges.length)
+ return;
+ var primarySelection = selection.ranges[0];
+ this._reportJump(this.selection(), this._toRange(primarySelection.anchor, primarySelection.head));
},
/**
@@ -1080,6 +1112,20 @@
},
/**
+ * @return {!Array.<!WebInspector.TextRange>}
+ */
+ selections: function()
+ {
+ var selectionList = this._codeMirror.listSelections();
+ var result = [];
+ for (var i = 0; i < selectionList.length; ++i) {
+ var selection = selectionList[i];
+ result.push(this._toRange(selection.anchor, selection.head));
+ }
+ return result;
+ },
+
+ /**
* @return {?WebInspector.TextRange}
*/
lastSelection: function()
@@ -1098,6 +1144,24 @@
},
/**
+ * @param {!Array.<!WebInspector.TextRange>} ranges
+ * @param {number=} primarySelectionIndex
+ */
+ setSelections: function(ranges, primarySelectionIndex)
+ {
+ var selections = [];
+ for (var i = 0; i < ranges.length; ++i) {
+ var selection = this._toPos(ranges[i]);
+ selections.push({
+ anchor: selection.start,
+ head: selection.end
+ });
+ }
+ primarySelectionIndex = primarySelectionIndex || 0;
+ this._codeMirror.setSelections(selections, primarySelectionIndex, { scroll: false });
+ },
+
+ /**
* @param {string} text
*/
_detectLineSeparator: function(text)
@@ -1271,10 +1335,12 @@
/**
* @constructor
+ * @param {!WebInspector.CodeMirrorTextEditor} textEditor
* @param {!CodeMirror} codeMirror
*/
-WebInspector.CodeMirrorTextEditor.TokenHighlighter = function(codeMirror)
+WebInspector.CodeMirrorTextEditor.TokenHighlighter = function(textEditor, codeMirror)
{
+ this._textEditor = textEditor;
this._codeMirror = codeMirror;
}
@@ -1334,7 +1400,10 @@
if (selectionStart.ch === selectionEnd.ch)
return;
- var selectedText = this._codeMirror.getSelection();
+ var selections = this._codeMirror.getSelections();
+ if (selections.length > 1)
+ return;
+ var selectedText = selections[0];
if (this._isWord(selectedText, selectionStart.line, selectionStart.ch, selectionEnd.ch)) {
if (selectionStart)
this._codeMirror.addLineClass(selectionStart.line, "wrap", "cm-line-with-selection")
@@ -1499,34 +1568,36 @@
{
function moveLeft(shift, codeMirror)
{
- var cursor = codeMirror.getCursor("head");
- if (cursor.ch !== 0 || cursor.line === 0)
- return CodeMirror.Pass;
codeMirror.setExtending(shift);
- codeMirror.execCommand("goLineUp");
- codeMirror.execCommand("goLineEnd")
+ var cursor = codeMirror.getCursor("head");
+ codeMirror.execCommand("goGroupLeft");
+ var newCursor = codeMirror.getCursor("head");
+ if (newCursor.ch === 0 && newCursor.line !== 0) {
+ codeMirror.setExtending(false);
+ return;
+ }
+
+ var skippedText = codeMirror.getRange(newCursor, cursor, "#");
+ if (/^\s+$/.test(skippedText))
+ codeMirror.execCommand("goGroupLeft");
codeMirror.setExtending(false);
}
+
function moveRight(shift, codeMirror)
{
- var cursor = codeMirror.getCursor("head");
- var line = codeMirror.getLine(cursor.line);
- if (cursor.ch !== line.length || cursor.line + 1 === codeMirror.lineCount())
- return CodeMirror.Pass;
codeMirror.setExtending(shift);
- codeMirror.execCommand("goLineDown");
- codeMirror.execCommand("goLineStart");
- codeMirror.setExtending(false);
- }
- function delWordBack(codeMirror)
- {
- if (codeMirror.somethingSelected())
- return CodeMirror.Pass;
var cursor = codeMirror.getCursor("head");
- if (cursor.ch === 0)
- codeMirror.execCommand("delCharBefore");
- else
- return CodeMirror.Pass;
+ codeMirror.execCommand("goGroupRight");
+ var newCursor = codeMirror.getCursor("head");
+ if (newCursor.ch === 0 && newCursor.line !== 0) {
+ codeMirror.setExtending(false);
+ return;
+ }
+
+ var skippedText = codeMirror.getRange(cursor, newCursor, "#");
+ if (/^\s+$/.test(skippedText))
+ codeMirror.execCommand("goGroupRight");
+ codeMirror.setExtending(false);
}
var modifierKey = WebInspector.isMac() ? "Alt" : "Ctrl";
@@ -1537,7 +1608,6 @@
keyMap[rightKey] = moveRight.bind(null, false);
keyMap["Shift-" + leftKey] = moveLeft.bind(null, true);
keyMap["Shift-" + rightKey] = moveRight.bind(null, true);
- keyMap[modifierKey + "-Backspace"] = delWordBack;
codeMirror.addKeyMap(keyMap);
}
@@ -1556,6 +1626,25 @@
}
WebInspector.CodeMirrorTextEditor.AutocompleteController.prototype = {
+ /**
+ * @param {!WebInspector.TextRange} mainSelection
+ * @param {!Array.<!{head: !CodeMirror.Pos, anchor: !CodeMirror.Pos}>} selections
+ * @return {boolean}
+ */
+ _validateSelectionsContexts: function(mainSelection, selections)
+ {
+ var mainSelectionContext = this._textEditor.copyRange(mainSelection);
+ for (var i = 0; i < selections.length; ++i) {
+ var wordRange = this._textEditor._wordRangeForCursorPosition(selections[i].head.line, selections[i].head.ch);
+ if (!wordRange)
+ return false;
+ var context = this._textEditor.copyRange(wordRange);
+ if (context !== mainSelectionContext)
+ return false;
+ }
+ return true;
+ },
+
autocomplete: function()
{
var dictionary = this._textEditor._dictionary;
@@ -1564,12 +1653,15 @@
return;
}
- var cursor = this._codeMirror.getCursor();
- var substituteRange = this._textEditor._wordRangeForCursorPosition(cursor.line, cursor.ch, false);
- if (!substituteRange || substituteRange.startColumn === cursor.ch) {
+ var selections = this._codeMirror.listSelections().slice();
+ var topSelection = selections.shift();
+ var cursor = topSelection.head;
+ var substituteRange = this._textEditor._wordRangeForCursorPosition(cursor.line, cursor.ch);
+ if (!substituteRange || substituteRange.startColumn === cursor.ch || !this._validateSelectionsContexts(substituteRange, selections)) {
this.finishAutocomplete();
return;
}
+
var prefixRange = substituteRange.clone();
prefixRange.endColumn = cursor.ch;
@@ -1639,9 +1731,15 @@
acceptSuggestion: function()
{
- if (this._prefixRange.endColumn - this._prefixRange.startColumn !== this._currentSuggestion.length) {
- var pos = this._textEditor._toPos(this._prefixRange);
- this._codeMirror.replaceRange(this._currentSuggestion, pos.start, pos.end, "+autocomplete");
+ if (this._prefixRange.endColumn - this._prefixRange.startColumn === this._currentSuggestion.length)
+ return;
+
+ var selections = this._codeMirror.listSelections().slice();
+ var prefixLength = this._prefixRange.endColumn - this._prefixRange.startColumn;
+ for (var i = selections.length - 1; i >= 0; --i) {
+ var start = selections[i].head;
+ var end = new CodeMirror.Pos(start.line, start.ch - prefixLength);
+ this._codeMirror.replaceRange(this._currentSuggestion, start, end, "+autocomplete");
}
},
@@ -1666,7 +1764,7 @@
if (!this._suggestBox)
return;
var cursor = this._codeMirror.getCursor();
- if (cursor.line !== this._prefixRange.startLine || cursor.ch > this._prefixRange.endColumn || cursor.ch < this._prefixRange.startColumn)
+ if (cursor.line !== this._prefixRange.startLine || cursor.ch > this._prefixRange.endColumn || cursor.ch <= this._prefixRange.startColumn)
this.finishAutocomplete();
},
@@ -1683,6 +1781,139 @@
}
/**
+ * @constructor
+ * @param {!WebInspector.CodeMirrorTextEditor} textEditor
+ * @param {!CodeMirror} codeMirror
+ */
+WebInspector.CodeMirrorTextEditor.SelectNextOccurrenceController = function(textEditor, codeMirror)
+{
+ this._textEditor = textEditor;
+ this._codeMirror = codeMirror;
+}
+
+WebInspector.CodeMirrorTextEditor.SelectNextOccurrenceController.prototype = {
+ selectionWillChange: function()
+ {
+ if (!this._muteSelectionListener)
+ delete this._fullWordSelection;
+ },
+
+ /**
+ * @param {!Array.<!WebInspector.TextRange>} selections
+ * @param {!WebInspector.TextRange} range
+ * @return {boolean}
+ */
+ _findRange: function(selections, range)
+ {
+ for (var i = 0; i < selections.length; ++i) {
+ if (range.equal(selections[i]))
+ return true;
+ }
+ return false;
+ },
+
+ selectNextOccurrence: function()
+ {
+ var selections = this._textEditor.selections();
+ var anyEmptySelection = false;
+ for (var i = 0; i < selections.length; ++i) {
+ var selection = selections[i];
+ anyEmptySelection = anyEmptySelection || selection.isEmpty();
+ if (selection.startLine !== selection.endLine)
+ return;
+ }
+ if (anyEmptySelection) {
+ this._expandSelectionsToWords(selections);
+ return;
+ }
+
+ var last = selections[selections.length - 1];
+ var next = last;
+ do {
+ next = this._findNextOccurrence(next, !!this._fullWordSelection);
+ } while (next && this._findRange(selections, next) && !next.equal(last));
+
+ if (!next)
+ return;
+ selections.push(next);
+
+ this._muteSelectionListener = true;
+ this._textEditor.setSelections(selections, selections.length - 1);
+ delete this._muteSelectionListener;
+
+ this._textEditor._revealLine(next.startLine);
+ },
+
+ /**
+ * @param {!Array.<!WebInspector.TextRange>} selections
+ */
+ _expandSelectionsToWords: function(selections)
+ {
+ var newSelections = [];
+ for (var i = 0; i < selections.length; ++i) {
+ var selection = selections[i];
+ var startRangeWord = this._textEditor._wordRangeForCursorPosition(selection.startLine, selection.startColumn)
+ || WebInspector.TextRange.createFromLocation(selection.startLine, selection.startColumn);
+ var endRangeWord = this._textEditor._wordRangeForCursorPosition(selection.endLine, selection.endColumn)
+ || WebInspector.TextRange.createFromLocation(selection.endLine, selection.endColumn);
+ var newSelection = new WebInspector.TextRange(startRangeWord.startLine, startRangeWord.startColumn, endRangeWord.endLine, endRangeWord.endColumn);
+ newSelections.push(newSelection);
+ }
+ this._textEditor.setSelections(newSelections, newSelections.length - 1);
+ this._fullWordSelection = true;
+ },
+
+ /**
+ * @param {!WebInspector.TextRange} range
+ * @param {boolean} fullWord
+ * @return {?WebInspector.TextRange}
+ */
+ _findNextOccurrence: function(range, fullWord)
+ {
+ range = range.normalize();
+ var matchedLineNumber;
+ var matchedColumnNumber;
+ var textToFind = this._textEditor.copyRange(range);
+ function findWordInLine(wordRegex, lineNumber, lineText, from, to)
+ {
+ if (typeof matchedLineNumber === "number")
+ return true;
+ wordRegex.lastIndex = from;
+ var result = wordRegex.exec(lineText);
+ if (!result || result.index + textToFind.length > to)
+ return false;
+ matchedLineNumber = lineNumber;
+ matchedColumnNumber = result.index;
+ return true;
+ }
+
+ var iteratedLineNumber;
+ function lineIterator(regex, lineHandle)
+ {
+ if (findWordInLine(regex, iteratedLineNumber++, lineHandle.text, 0, lineHandle.text.length))
+ return true;
+ }
+
+ var regexSource = textToFind.escapeForRegExp();
+ if (fullWord)
+ regexSource = "\\b" + regexSource + "\\b";
+ var wordRegex = new RegExp(regexSource, "gi");
+ var currentLineText = this._codeMirror.getLine(range.startLine);
+
+ findWordInLine(wordRegex, range.startLine, currentLineText, range.endColumn, currentLineText.length);
+ iteratedLineNumber = range.startLine + 1;
+ this._codeMirror.eachLine(range.startLine + 1, this._codeMirror.lineCount(), lineIterator.bind(null, wordRegex));
+ iteratedLineNumber = 0;
+ this._codeMirror.eachLine(0, range.startLine, lineIterator.bind(null, wordRegex));
+ findWordInLine(wordRegex, range.startLine, currentLineText, 0, range.startColumn);
+
+ if (typeof matchedLineNumber !== "number")
+ return null;
+ return new WebInspector.TextRange(matchedLineNumber, matchedColumnNumber, matchedLineNumber, matchedColumnNumber + textToFind.length);
+ }
+}
+
+/**
* @param {string} modeName
* @param {string} tokenPrefix
*/
diff --git a/Source/devtools/front_end/CodeMirrorUtils.js b/Source/devtools/front_end/CodeMirrorUtils.js
index d03686a..7db1f68 100644
--- a/Source/devtools/front_end/CodeMirrorUtils.js
+++ b/Source/devtools/front_end/CodeMirrorUtils.js
@@ -72,7 +72,7 @@
});
codeMirror.getWrapperElement().classList.add("source-code");
codeMirror.on("cursorActivity", function(cm) {
- cm.display.cursor.scrollIntoViewIfNeeded(false);
+ cm.display.cursorDiv.scrollIntoViewIfNeeded(false);
});
editingContext.codeMirror = codeMirror;
},
diff --git a/Source/devtools/front_end/ConsoleModel.js b/Source/devtools/front_end/ConsoleModel.js
index a63d5a4..470ad38 100644
--- a/Source/devtools/front_end/ConsoleModel.js
+++ b/Source/devtools/front_end/ConsoleModel.js
@@ -30,16 +30,17 @@
/**
* @constructor
- * @extends {WebInspector.Object}
+ * @extends {WebInspector.TargetAwareObject}
* @param {!WebInspector.Target} target
*/
WebInspector.ConsoleModel = function(target)
{
+ WebInspector.TargetAwareObject.call(this, target);
+
/** @type {!Array.<!WebInspector.ConsoleMessage>} */
this.messages = [];
this.warnings = 0;
this.errors = 0;
- this._target = target;
this._consoleAgent = target.consoleAgent();
target.registerConsoleDispatcher(new WebInspector.ConsoleDispatcher(this));
this._enableAgent();
@@ -101,7 +102,7 @@
{
this.show();
- var commandMessage = new WebInspector.ConsoleMessage(WebInspector.ConsoleMessage.MessageSource.JS, null, text, WebInspector.ConsoleMessage.MessageType.Command);
+ var commandMessage = new WebInspector.ConsoleMessage(this.target(), WebInspector.ConsoleMessage.MessageSource.JS, null, text, WebInspector.ConsoleMessage.MessageType.Command);
this.addMessage(commandMessage);
/**
@@ -117,7 +118,7 @@
this.dispatchEventToListeners(WebInspector.ConsoleModel.Events.CommandEvaluated, {result: result, wasThrown: wasThrown, text: text, commandMessage: commandMessage});
}
- this._target.runtimeModel.evaluate(text, "console", useCommandLineAPI, false, false, true, printResult.bind(this));
+ this.target().runtimeModel.evaluate(text, "console", useCommandLineAPI, false, false, true, printResult.bind(this));
WebInspector.userMetrics.ConsoleEvaluated.record();
},
@@ -143,6 +144,7 @@
log: function(messageText, messageLevel, showConsole)
{
var message = new WebInspector.ConsoleMessage(
+ this.target(),
WebInspector.ConsoleMessage.MessageSource.Other,
messageLevel || WebInspector.ConsoleMessage.MessageLevel.Debug,
messageText);
@@ -190,11 +192,13 @@
this.warnings = 0;
},
- __proto__: WebInspector.Object.prototype
+ __proto__: WebInspector.TargetAwareObject.prototype
}
/**
* @constructor
+ * @extends {WebInspector.TargetAware}
+ * @param {!WebInspector.Target} target
* @param {string} source
* @param {?string} level
* @param {string} messageText
@@ -207,10 +211,11 @@
* @param {!Array.<!ConsoleAgent.CallFrame>=} stackTrace
* @param {number=} timestamp
* @param {boolean=} isOutdated
+ * @param {!RuntimeAgent.ExecutionContextId=} executionContextId
*/
-
-WebInspector.ConsoleMessage = function(source, level, messageText, type, url, line, column, requestId, parameters, stackTrace, timestamp, isOutdated)
+WebInspector.ConsoleMessage = function(target, source, level, messageText, type, url, line, column, requestId, parameters, stackTrace, timestamp, isOutdated, executionContextId)
{
+ WebInspector.TargetAware.call(this, target);
this.source = source;
this.level = level;
this.messageText = messageText;
@@ -222,6 +227,7 @@
this.stackTrace = stackTrace;
this.timestamp = timestamp || Date.now();
this.isOutdated = isOutdated;
+ this.executionContextId = executionContextId;
this.request = requestId ? WebInspector.networkLog.requestForId(requestId) : null;
}
@@ -252,6 +258,7 @@
clone: function()
{
return new WebInspector.ConsoleMessage(
+ this.target(),
this.source,
this.level,
this.messageText,
@@ -263,7 +270,8 @@
this.parameters,
this.stackTrace,
this.timestamp,
- this.isOutdated);
+ this.isOutdated,
+ this.executionContextId);
},
/**
@@ -306,7 +314,9 @@
&& (this.url === msg.url)
&& (this.messageText === msg.messageText)
&& (this.request === msg.request);
- }
+ },
+
+ __proto__: WebInspector.TargetAware.prototype
}
// Note: Keep these constants in sync with the ones in Console.h
@@ -375,6 +385,7 @@
messageAdded: function(payload)
{
var consoleMessage = new WebInspector.ConsoleMessage(
+ this._console.target(),
payload.source,
payload.level,
payload.text,
@@ -386,7 +397,8 @@
payload.parameters,
payload.stackTrace,
payload.timestamp * 1000, // Convert to ms.
- this._console._enablingConsole);
+ this._console._enablingConsole,
+ payload.executionContextId);
this._console.addMessage(consoleMessage, true);
},
diff --git a/Source/devtools/front_end/ConsoleView.js b/Source/devtools/front_end/ConsoleView.js
index ba97e7c..58a7800 100644
--- a/Source/devtools/front_end/ConsoleView.js
+++ b/Source/devtools/front_end/ConsoleView.js
@@ -31,6 +31,7 @@
* @extends {WebInspector.VBox}
* @implements {WebInspector.Searchable}
* @constructor
+ * @implements {WebInspector.TargetManager.Observer}
* @param {boolean} hideContextSelector
*/
WebInspector.ConsoleView = function(hideContextSelector)
@@ -112,8 +113,7 @@
this.prompt.proxyElement.addEventListener("keydown", this._promptKeyDown.bind(this), false);
this.prompt.setHistoryData(WebInspector.settings.consoleHistory.get());
- WebInspector.targetManager.targets().forEach(this._targetAdded, this);
- WebInspector.targetManager.addEventListener(WebInspector.TargetManager.Events.TargetAdded, this._onTargetAdded, this);
+ WebInspector.targetManager.observeTargets(this);
this._filterStatusMessageElement = document.createElement("div");
this._filterStatusMessageElement.classList.add("console-message");
@@ -131,22 +131,14 @@
WebInspector.ConsoleView.prototype = {
/**
- * @param {!WebInspector.Event} event
- */
- _onTargetAdded: function(event)
- {
- this._targetAdded(/**@type {!WebInspector.Target} */(event.data));
- },
-
- /**
* @param {!WebInspector.Target} target
*/
- _targetAdded: function(target)
+ targetAdded: function(target)
{
- target.consoleModel.addEventListener(WebInspector.ConsoleModel.Events.MessageAdded, this._onConsoleMessageAdded.bind(this, target), this);
+ target.consoleModel.addEventListener(WebInspector.ConsoleModel.Events.MessageAdded, this._onConsoleMessageAdded, this);
target.consoleModel.addEventListener(WebInspector.ConsoleModel.Events.ConsoleCleared, this._consoleCleared, this);
target.consoleModel.addEventListener(WebInspector.ConsoleModel.Events.CommandEvaluated, this._commandEvaluated, this);
- target.consoleModel.messages.forEach(this._consoleMessageAdded.bind(this, target));
+ target.consoleModel.messages.forEach(this._consoleMessageAdded, this);
/**
* @param {!WebInspector.ExecutionContextList} contextList
@@ -163,6 +155,20 @@
},
+ /**
+ * @param {!WebInspector.Target} target
+ */
+ targetRemoved: function(target)
+ {
+ },
+
+ /**
+ * @param {?WebInspector.Target} target
+ */
+ activeTargetChanged: function(target)
+ {
+ },
+
_consoleTimestampsSettingChanged: function(event)
{
var enabled = /** @type {boolean} */ (event.data);
@@ -266,7 +272,7 @@
_currentTarget: function()
{
var option = this._executionContextSelector.selectedOption();
- return option ? option._target : WebInspector.targetManager.mainTarget();
+ return option ? option._target : WebInspector.targetManager.activeTarget();
},
/**
@@ -423,7 +429,7 @@
/**
* @param {!WebInspector.ConsoleMessage} message
*/
- _consoleMessageAdded: function(target, message)
+ _consoleMessageAdded: function(message)
{
if (this._urlToMessageCount[message.url])
this._urlToMessageCount[message.url]++;
@@ -438,7 +444,7 @@
}
this._consoleMessages.push(message);
- var viewMessage = this._createViewMessage(target, message);
+ var viewMessage = this._createViewMessage(message);
if (this._filter.shouldBeVisible(viewMessage))
this._showConsoleMessage(viewMessage);
@@ -449,10 +455,10 @@
/**
* @param {!WebInspector.Event} event
*/
- _onConsoleMessageAdded: function(target, event)
+ _onConsoleMessageAdded: function(event)
{
var message = /** @type {!WebInspector.ConsoleMessage} */ (event.data);
- this._consoleMessageAdded(target, message);
+ this._consoleMessageAdded(message);
},
/**
@@ -493,15 +499,15 @@
* @param {!WebInspector.ConsoleMessage} message
* @return {!WebInspector.ConsoleViewMessage}
*/
- _createViewMessage: function(target, message)
+ _createViewMessage: function(message)
{
var viewMessage = this._messageToViewMessage.get(message);
if (viewMessage)
return viewMessage;
if (message.type === WebInspector.ConsoleMessage.MessageType.Command)
- viewMessage = new WebInspector.ConsoleCommand(target, message);
+ viewMessage = new WebInspector.ConsoleCommand(message);
else
- viewMessage = new WebInspector.ConsoleViewMessage(target, message, this._linkifier);
+ viewMessage = new WebInspector.ConsoleViewMessage(message, this._linkifier);
this._messageToViewMessage.put(message, viewMessage);
return viewMessage;
},
@@ -750,17 +756,15 @@
return;
}
- target.debuggerAgent().getFunctionDetails(result.objectId, didGetDetails.bind(this));
+ result.functionDetails(didGetDetails.bind(this));
/**
- * @param {?Protocol.Error} error
- * @param {!DebuggerAgent.FunctionDetails} response
+ * @param {?DebuggerAgent.FunctionDetails} response
* @this {WebInspector.ConsoleView}
*/
- function didGetDetails(error, response)
+ function didGetDetails(response)
{
- if (error) {
- console.error(error);
+ if (!response) {
addMessage.call(this);
return;
}
@@ -768,12 +772,13 @@
var url;
var lineNumber;
var columnNumber;
- var script = WebInspector.debuggerModel.scriptForId(response.location.scriptId);
+ var script = target.debuggerModel.scriptForId(response.location.scriptId);
if (script && script.sourceURL) {
url = script.sourceURL;
lineNumber = response.location.lineNumber + 1;
columnNumber = response.location.columnNumber + 1;
}
+ // FIXME: this should be using live location.
addMessage.call(this, url, lineNumber, columnNumber);
}
},
@@ -995,9 +1000,9 @@
* @extends {WebInspector.ConsoleViewMessage}
* @param {!WebInspector.ConsoleMessage} message
*/
-WebInspector.ConsoleCommand = function(target, message)
+WebInspector.ConsoleCommand = function(message)
{
- WebInspector.ConsoleViewMessage.call(this, target, message, null);
+ WebInspector.ConsoleViewMessage.call(this, message, null);
}
WebInspector.ConsoleCommand.prototype = {
@@ -1070,8 +1075,8 @@
}
/**
- * @extends {WebInspector.ConsoleViewMessage}
* @constructor
+ * @extends {WebInspector.ConsoleViewMessage}
* @param {!WebInspector.RemoteObject} result
* @param {boolean} wasThrown
* @param {?WebInspector.ConsoleCommand} originatingCommand
@@ -1084,9 +1089,8 @@
{
this.originatingCommand = originatingCommand;
var level = wasThrown ? WebInspector.ConsoleMessage.MessageLevel.Error : WebInspector.ConsoleMessage.MessageLevel.Log;
-
- var message = new WebInspector.ConsoleMessage(WebInspector.ConsoleMessage.MessageSource.JS, level, "", WebInspector.ConsoleMessage.MessageType.Result, url, lineNumber, columnNumber, undefined, [result]);
- WebInspector.ConsoleViewMessage.call(this, result.target(), message, linkifier);
+ var message = new WebInspector.ConsoleMessage(/** @type {!WebInspector.Target} */ (result.target()), WebInspector.ConsoleMessage.MessageSource.JS, level, "", WebInspector.ConsoleMessage.MessageType.Result, url, lineNumber, columnNumber, undefined, [result]);
+ WebInspector.ConsoleViewMessage.call(this, message, linkifier);
}
WebInspector.ConsoleCommandResult.prototype = {
diff --git a/Source/devtools/front_end/ConsoleViewMessage.js b/Source/devtools/front_end/ConsoleViewMessage.js
index 1e9b0b3..1374ed7 100644
--- a/Source/devtools/front_end/ConsoleViewMessage.js
+++ b/Source/devtools/front_end/ConsoleViewMessage.js
@@ -30,15 +30,15 @@
/**
* @constructor
- * @param {!WebInspector.Target} target
+ * @extends {WebInspector.TargetAware}
* @param {!WebInspector.ConsoleMessage} consoleMessage
* @param {?WebInspector.Linkifier} linkifier
*/
-WebInspector.ConsoleViewMessage = function(target, consoleMessage, linkifier)
+WebInspector.ConsoleViewMessage = function(consoleMessage, linkifier)
{
+ WebInspector.TargetAware.call(this, consoleMessage.target());
this._message = consoleMessage;
this._linkifier = linkifier;
- this._target = target;
this._repeatCount = 1;
/** @type {!Array.<!WebInspector.DataGrid>} */
@@ -223,8 +223,8 @@
lineNumber = lineNumber ? lineNumber - 1 : 0;
columnNumber = columnNumber ? columnNumber - 1 : 0;
if (this._message.source === WebInspector.ConsoleMessage.MessageSource.CSS) {
- var headerIds = WebInspector.cssModel.styleSheetIdsForURL(url);
- var cssLocation = new WebInspector.CSSLocation(url, lineNumber, columnNumber);
+ var headerIds = this.target().cssModel.styleSheetIdsForURL(url);
+ var cssLocation = new WebInspector.CSSLocation(this.target(), url, lineNumber, columnNumber);
return this._linkifier.linkifyCSSLocation(headerIds[0] || null, cssLocation, "console-message-url");
}
@@ -243,7 +243,7 @@
// FIXME(62725): stack trace line/column numbers are one-based.
var lineNumber = callFrame.lineNumber ? callFrame.lineNumber - 1 : 0;
var columnNumber = callFrame.columnNumber ? callFrame.columnNumber - 1 : 0;
- var rawLocation = new WebInspector.DebuggerModel.Location(callFrame.scriptId, lineNumber, columnNumber);
+ var rawLocation = new WebInspector.DebuggerModel.Location(this.target(), callFrame.scriptId, lineNumber, columnNumber);
return this._linkifier.linkifyRawLocation(rawLocation, "console-message-url");
},
@@ -264,7 +264,7 @@
if (!regex)
return callFrame;
for (var i = 0; i < stackTrace.length; ++i) {
- var script = this._target.debuggerModel.scriptForId(stackTrace[i].scriptId);
+ var script = this.target().debuggerModel.scriptForId(stackTrace[i].scriptId);
if (!script || !regex.test(script.sourceURL))
return stackTrace[i].scriptId ? stackTrace[i] : null;
}
@@ -294,9 +294,9 @@
continue;
if (typeof parameters[i] === "object")
- parameters[i] = WebInspector.RemoteObject.fromPayload(parameters[i], this._target);
+ parameters[i] = this.target().runtimeModel.createRemoteObject(parameters[i]);
else
- parameters[i] = WebInspector.RemoteObject.fromPrimitiveValue(parameters[i], this._target);
+ parameters[i] = this.target().runtimeModel.createRemoteObjectFromPrimitiveValue(parameters[i]);
}
// There can be string log and string eval result. We distinguish between them based on message type.
@@ -498,7 +498,7 @@
this._formatParameterAsObject(object, elem, false);
return;
}
- var node = WebInspector.domModel.nodeForId(nodeId);
+ var node = this.target().domModel.nodeForId(nodeId);
var renderer = WebInspector.moduleManager.instance(WebInspector.Renderer, node);
if (renderer)
elem.appendChild(renderer.render(node));
@@ -1076,5 +1076,7 @@
get text()
{
return this._message.messageText;
- }
+ },
+
+ __proto__: WebInspector.TargetAware.prototype
}
diff --git a/Source/devtools/front_end/DOMBreakpointsSidebarPane.js b/Source/devtools/front_end/DOMBreakpointsSidebarPane.js
index ba0ebf7..6129846 100644
--- a/Source/devtools/front_end/DOMBreakpointsSidebarPane.js
+++ b/Source/devtools/front_end/DOMBreakpointsSidebarPane.js
@@ -103,13 +103,16 @@
}
},
- createBreakpointHitStatusMessage: function(auxData, callback)
+ /**
+ * @param {!WebInspector.DebuggerPausedDetails} details
+ */
+ createBreakpointHitStatusMessage: function(details, callback)
{
- if (auxData.type === this._breakpointTypes.SubtreeModified) {
- var targetNodeObject = WebInspector.RemoteObject.fromPayload(auxData["targetNode"]);
+ if (details.auxData.type === this._breakpointTypes.SubtreeModified) {
+ var targetNodeObject = details.target().runtimeModel.createRemoteObject(details.auxData["targetNode"]);
targetNodeObject.pushNodeToFrontend(didPushNodeToFrontend.bind(this));
} else
- this._doCreateBreakpointHitStatusMessage(auxData, null, callback);
+ this._doCreateBreakpointHitStatusMessage(details.auxData, null, callback);
/**
* @param {?DOMAgent.NodeId} targetNodeId
@@ -119,7 +122,7 @@
{
if (targetNodeId)
targetNodeObject.release();
- this._doCreateBreakpointHitStatusMessage(auxData, targetNodeId, callback);
+ this._doCreateBreakpointHitStatusMessage(details.auxData, targetNodeId, callback);
}
},
diff --git a/Source/devtools/front_end/DOMExtension.js b/Source/devtools/front_end/DOMExtension.js
index 70df32c..875d8c9 100644
--- a/Source/devtools/front_end/DOMExtension.js
+++ b/Source/devtools/front_end/DOMExtension.js
@@ -215,6 +215,116 @@
};
/**
+ * @param {!Size|number} size
+ * @return {!Size}
+ */
+Size.prototype.widthToMax = function(size)
+{
+ return new Size(Math.max(this.width, (typeof size === "number" ? size : size.width)), this.height);
+};
+
+/**
+ * @param {!Size|number} size
+ * @return {!Size}
+ */
+Size.prototype.addWidth = function(size)
+{
+ return new Size(this.width + (typeof size === "number" ? size : size.width), this.height);
+};
+
+/**
+ * @param {!Size|number} size
+ * @return {!Size}
+ */
+Size.prototype.heightToMax = function(size)
+{
+ return new Size(this.width, Math.max(this.height, (typeof size === "number" ? size : size.height)));
+};
+
+/**
+ * @param {!Size|number} size
+ * @return {!Size}
+ */
+Size.prototype.addHeight = function(size)
+{
+ return new Size(this.width, this.height + (typeof size === "number" ? size : size.height));
+};
+
+/**
+ * @constructor
+ * @param {!Size} minimum
+ * @param {?Size=} preferred
+ */
+function Constraints(minimum, preferred)
+{
+ /**
+ * @type {!Size}
+ */
+ this.minimum = minimum;
+
+ /**
+ * @type {!Size}
+ */
+ this.preferred = preferred || minimum;
+
+ if (this.minimum.width > this.preferred.width || this.minimum.height > this.preferred.height)
+ throw new Error("Minimum size is greater than preferred.");
+}
+
+/**
+ * @param {?Constraints} constraints
+ * @return {boolean}
+ */
+Constraints.prototype.isEqual = function(constraints)
+{
+ return !!constraints && this.minimum.isEqual(constraints.minimum) && this.preferred.isEqual(constraints.preferred);
+};
+
+/**
+ * @param {!Constraints|number} value
+ * @return {!Constraints}
+ */
+Constraints.prototype.widthToMax = function(value)
+{
+ if (typeof value === "number")
+ return new Constraints(this.minimum.widthToMax(value), this.preferred.widthToMax(value));
+ return new Constraints(this.minimum.widthToMax(value.minimum), this.preferred.widthToMax(value.preferred));
+};
+
+/**
+ * @param {!Constraints|number} value
+ * @return {!Constraints}
+ */
+Constraints.prototype.addWidth = function(value)
+{
+ if (typeof value === "number")
+ return new Constraints(this.minimum.addWidth(value), this.preferred.addWidth(value));
+ return new Constraints(this.minimum.addWidth(value.minimum), this.preferred.addWidth(value.preferred));
+};
+
+/**
+ * @param {!Constraints|number} value
+ * @return {!Constraints}
+ */
+Constraints.prototype.heightToMax = function(value)
+{
+ if (typeof value === "number")
+ return new Constraints(this.minimum.heightToMax(value), this.preferred.heightToMax(value));
+ return new Constraints(this.minimum.heightToMax(value.minimum), this.preferred.heightToMax(value.preferred));
+};
+
+/**
+ * @param {!Constraints|number} value
+ * @return {!Constraints}
+ */
+Constraints.prototype.addHeight = function(value)
+{
+ if (typeof value === "number")
+ return new Constraints(this.minimum.addHeight(value), this.preferred.addHeight(value));
+ return new Constraints(this.minimum.addHeight(value.minimum), this.preferred.addHeight(value.preferred));
+};
+
+/**
* @param {?Element=} containerElement
* @return {!Size}
*/
diff --git a/Source/devtools/front_end/DOMModel.js b/Source/devtools/front_end/DOMModel.js
index baf27d8..cc31a23 100644
--- a/Source/devtools/front_end/DOMModel.js
+++ b/Source/devtools/front_end/DOMModel.js
@@ -31,13 +31,16 @@
/**
* @constructor
+ * @extends {WebInspector.TargetAware}
* @param {!WebInspector.DOMModel} domModel
* @param {?WebInspector.DOMDocument} doc
* @param {boolean} isInShadowTree
* @param {!DOMAgent.Node} payload
*/
WebInspector.DOMNode = function(domModel, doc, isInShadowTree, payload) {
+ WebInspector.TargetAware.call(this, domModel.target());
this._domModel = domModel;
+ this._agent = domModel._agent;
this.ownerDocument = doc;
this._isInShadowTree = isInShadowTree;
@@ -134,6 +137,14 @@
WebInspector.DOMNode.prototype = {
/**
+ * @return {!WebInspector.DOMModel}
+ */
+ domModel: function()
+ {
+ return this._domModel;
+ },
+
+ /**
* @return {?Array.<!WebInspector.DOMNode>}
*/
children: function()
@@ -284,7 +295,7 @@
*/
setNodeName: function(name, callback)
{
- DOMAgent.setNodeName(this.id, name, WebInspector.domModel._markRevision(this, callback));
+ this._agent.setNodeName(this.id, name, this._domModel._markRevision(this, callback));
},
/**
@@ -309,7 +320,7 @@
*/
setNodeValue: function(value, callback)
{
- DOMAgent.setNodeValue(this.id, value, WebInspector.domModel._markRevision(this, callback));
+ this._agent.setNodeValue(this.id, value, this._domModel._markRevision(this, callback));
},
/**
@@ -329,7 +340,7 @@
*/
setAttribute: function(name, text, callback)
{
- DOMAgent.setAttributesAsText(this.id, text, name, WebInspector.domModel._markRevision(this, callback));
+ this._agent.setAttributesAsText(this.id, text, name, this._domModel._markRevision(this, callback));
},
/**
@@ -339,7 +350,7 @@
*/
setAttributeValue: function(name, value, callback)
{
- DOMAgent.setAttributeValue(this.id, name, value, WebInspector.domModel._markRevision(this, callback));
+ this._agent.setAttributeValue(this.id, name, value, this._domModel._markRevision(this, callback));
},
/**
@@ -372,9 +383,9 @@
}
}
- WebInspector.domModel._markRevision(this, callback)(error);
+ this._domModel._markRevision(this, callback)(error);
}
- DOMAgent.removeAttribute(this.id, name, mycallback.bind(this));
+ this._agent.removeAttribute(this.id, name, mycallback.bind(this));
},
/**
@@ -398,7 +409,7 @@
callback(error ? null : this.children());
}
- DOMAgent.requestChildNodes(this.id, undefined, mycallback.bind(this));
+ this._agent.requestChildNodes(this.id, undefined, mycallback.bind(this));
},
/**
@@ -417,7 +428,7 @@
callback(error ? null : this._children);
}
- DOMAgent.requestChildNodes(this.id, depth, mycallback.bind(this));
+ this._agent.requestChildNodes(this.id, depth, mycallback.bind(this));
},
/**
@@ -425,7 +436,7 @@
*/
getOuterHTML: function(callback)
{
- DOMAgent.getOuterHTML(this.id, callback);
+ this._agent.getOuterHTML(this.id, callback);
},
/**
@@ -434,7 +445,7 @@
*/
setOuterHTML: function(html, callback)
{
- DOMAgent.setOuterHTML(this.id, html, WebInspector.domModel._markRevision(this, callback));
+ this._agent.setOuterHTML(this.id, html, this._domModel._markRevision(this, callback));
},
/**
@@ -442,7 +453,7 @@
*/
removeNode: function(callback)
{
- DOMAgent.removeNode(this.id, WebInspector.domModel._markRevision(this, callback));
+ this._agent.removeNode(this.id, this._domModel._markRevision(this, callback));
},
copyNode: function()
@@ -452,16 +463,32 @@
if (!error)
InspectorFrontendHost.copyText(text);
}
- DOMAgent.getOuterHTML(this.id, copy);
+ this._agent.getOuterHTML(this.id, copy);
},
/**
* @param {string} objectGroupId
- * @param {function(?Protocol.Error)=} callback
+ * @param {function(?Array.<!WebInspector.DOMModel.EventListener>)} callback
*/
eventListeners: function(objectGroupId, callback)
{
- DOMAgent.getEventListenersForNode(this.id, objectGroupId, callback);
+ var target = this.target();
+
+ /**
+ * @param {?Protocol.Error} error
+ * @param {!Array.<!DOMAgent.EventListener>} payloads
+ */
+ function mycallback(error, payloads)
+ {
+ if (error) {
+ callback(null);
+ return;
+ }
+ callback(payloads.map(function(payload) {
+ return new WebInspector.DOMModel.EventListener(target, payload);
+ }));
+ }
+ this._agent.getEventListenersForNode(this.id, objectGroupId, mycallback);
},
/**
@@ -684,7 +711,7 @@
*/
moveTo: function(targetNode, anchorNode, callback)
{
- DOMAgent.moveTo(this.id, targetNode.id, anchorNode ? anchorNode.id : undefined, WebInspector.domModel._markRevision(this, callback));
+ this._agent.moveTo(this.id, targetNode.id, anchorNode ? anchorNode.id : undefined, this._domModel._markRevision(this, callback));
},
/**
@@ -780,7 +807,53 @@
return WebInspector.ParsedURL.completeURL(frameOwnerCandidate.baseURL, url);
}
return null;
- }
+ },
+
+ /**
+ * @param {string=} mode
+ * @param {!RuntimeAgent.RemoteObjectId=} objectId
+ */
+ highlight: function(mode, objectId)
+ {
+ this._domModel.highlightDOMNode(this.id, mode, objectId);
+ },
+
+ highlightForTwoSeconds: function()
+ {
+ this._domModel.highlightDOMNodeForTwoSeconds(this.id);
+ },
+
+ reveal: function()
+ {
+ WebInspector.Revealer.reveal(this);
+ },
+
+ /**
+ * @param {string=} objectGroup
+ * @param {function(?WebInspector.RemoteObject)=} callback
+ */
+ resolveToObject: function(objectGroup, callback)
+ {
+ this._agent.resolveNode(this.id, objectGroup, mycallback.bind(this));
+
+ /**
+ * @param {?Protocol.Error} error
+ * @param {!RuntimeAgent.RemoteObject} object
+ * @this {WebInspector.DOMNode}
+ */
+ function mycallback(error, object)
+ {
+ if (!callback)
+ return;
+
+ if (error || !object)
+ callback(null);
+ else
+ callback(this.target().runtimeModel.createRemoteObject(object));
+ }
+ },
+
+ __proto__: WebInspector.TargetAware.prototype
}
/**
@@ -803,10 +876,15 @@
}
/**
- * @extends {WebInspector.Object}
* @constructor
+ * @extends {WebInspector.TargetAwareObject}
+ * @param {!WebInspector.Target} target
*/
-WebInspector.DOMModel = function() {
+WebInspector.DOMModel = function(target) {
+ WebInspector.TargetAwareObject.call(this, target);
+
+ this._agent = target.domAgent();
+
/** @type {!Object.<number, !WebInspector.DOMNode>} */
this._idToDOMNode = {};
/** @type {?WebInspector.DOMDocument} */
@@ -815,7 +893,7 @@
this._attributeLoadNodeIds = {};
InspectorBackend.registerDOMDispatcher(new WebInspector.DOMDispatcher(this));
- this._defaultHighlighter = new WebInspector.DefaultDOMNodeHighlighter();
+ this._defaultHighlighter = new WebInspector.DefaultDOMNodeHighlighter(this._agent);
this._highlighter = this._defaultHighlighter;
}
@@ -868,7 +946,7 @@
delete this._pendingDocumentRequestCallbacks;
}
- DOMAgent.getDocument(onDocumentAvailable.bind(this));
+ this._agent.getDocument(onDocumentAvailable.bind(this));
},
/**
@@ -885,7 +963,7 @@
*/
pushNodeToFrontend: function(objectId, callback)
{
- this._dispatchWhenDocumentAvailable(DOMAgent.requestNode.bind(DOMAgent, objectId), callback);
+ this._dispatchWhenDocumentAvailable(this._agent.requestNode.bind(this._agent, objectId), callback);
},
/**
@@ -894,7 +972,7 @@
*/
pushNodeByPathToFrontend: function(path, callback)
{
- this._dispatchWhenDocumentAvailable(DOMAgent.pushNodeByPathToFrontend.bind(DOMAgent, path), callback);
+ this._dispatchWhenDocumentAvailable(this._agent.pushNodeByPathToFrontend.bind(this._agent, path), callback);
},
/**
@@ -903,7 +981,7 @@
*/
pushNodesByBackendIdsToFrontend: function(backendNodeIds, callback)
{
- this._dispatchWhenDocumentAvailable(DOMAgent.pushNodesByBackendIdsToFrontend.bind(DOMAgent, backendNodeIds), callback);
+ this._dispatchWhenDocumentAvailable(this._agent.pushNodesByBackendIdsToFrontend.bind(this._agent, backendNodeIds), callback);
},
/**
@@ -1016,7 +1094,7 @@
for (var nodeId in this._attributeLoadNodeIds) {
var nodeIdAsNumber = parseInt(nodeId, 10);
- DOMAgent.getAttributes(nodeIdAsNumber, callback.bind(this, nodeIdAsNumber));
+ this._agent.getAttributes(nodeIdAsNumber, callback.bind(this, nodeIdAsNumber));
}
this._attributeLoadNodeIds = {};
},
@@ -1242,7 +1320,7 @@
this._searchId = searchId;
searchCallback(resultsCount);
}
- DOMAgent.performSearch(query, callback.bind(this));
+ this._agent.performSearch(query, callback.bind(this));
},
/**
@@ -1252,7 +1330,7 @@
searchResult: function(index, callback)
{
if (this._searchId)
- DOMAgent.getSearchResults(this._searchId, index, index + 1, searchResultsCallback.bind(this));
+ this._agent.getSearchResults(this._searchId, index, index + 1, searchResultsCallback.bind(this));
else
callback(null);
@@ -1278,7 +1356,7 @@
cancelSearch: function()
{
if (this._searchId) {
- DOMAgent.discardSearchResults(this._searchId);
+ this._agent.discardSearchResults(this._searchId);
delete this._searchId;
}
},
@@ -1290,7 +1368,7 @@
*/
querySelector: function(nodeId, selectors, callback)
{
- DOMAgent.querySelector(nodeId, selectors, this._wrapClientCallback(callback));
+ this._agent.querySelector(nodeId, selectors, this._wrapClientCallback(callback));
},
/**
@@ -1300,7 +1378,7 @@
*/
querySelectorAll: function(nodeId, selectors, callback)
{
- DOMAgent.querySelectorAll(nodeId, selectors, this._wrapClientCallback(callback));
+ this._agent.querySelectorAll(nodeId, selectors, this._wrapClientCallback(callback));
},
/**
@@ -1441,7 +1519,7 @@
markUndoableState: function()
{
- DOMAgent.markUndoableState();
+ this._agent.markUndoableState();
},
/**
@@ -1460,7 +1538,7 @@
}
this.dispatchEventToListeners(WebInspector.DOMModel.Events.UndoRedoRequested);
- DOMAgent.undo(callback);
+ this._agent.undo(callback);
},
/**
@@ -1479,7 +1557,7 @@
}
this.dispatchEventToListeners(WebInspector.DOMModel.Events.UndoRedoRequested);
- DOMAgent.redo(callback);
+ this._agent.redo(callback);
},
/**
@@ -1490,7 +1568,7 @@
this._highlighter = highlighter || this._defaultHighlighter;
},
- __proto__: WebInspector.Object.prototype
+ __proto__: WebInspector.TargetAwareObject.prototype
}
/**
@@ -1628,6 +1706,54 @@
}
/**
+ * @constructor
+ * @extends {WebInspector.TargetAware}
+ * @param {!WebInspector.Target} target
+ * @param {!DOMAgent.EventListener} payload
+ */
+WebInspector.DOMModel.EventListener = function(target, payload)
+{
+ WebInspector.TargetAware.call(this, target);
+ this._payload = payload;
+}
+
+WebInspector.DOMModel.EventListener.prototype = {
+ /**
+ * @return {!DOMAgent.EventListener}
+ */
+ payload: function()
+ {
+ return this._payload;
+ },
+
+ /**
+ * @return {?WebInspector.DOMNode}
+ */
+ node: function()
+ {
+ return this.target().domModel.nodeForId(this._payload.nodeId);
+ },
+
+ /**
+ * @return {!WebInspector.DebuggerModel.Location}
+ */
+ location: function()
+ {
+ return WebInspector.DebuggerModel.Location.fromPayload(this.target(), this._payload.location);
+ },
+
+ /**
+ * @return {?WebInspector.RemoteObject}
+ */
+ handler: function()
+ {
+ return this._payload.handler ? this.target().runtimeModel.createRemoteObject(this._payload.handler) : null;
+ },
+
+ __proto__: WebInspector.TargetAware.prototype
+}
+
+/**
* @interface
*/
WebInspector.DOMNodeHighlighter = function() {
@@ -1653,8 +1779,11 @@
/**
* @constructor
* @implements {WebInspector.DOMNodeHighlighter}
+ * @param {!Protocol.DOMAgent} agent
*/
-WebInspector.DefaultDOMNodeHighlighter = function() {
+WebInspector.DefaultDOMNodeHighlighter = function(agent)
+{
+ this._agent = agent;
}
WebInspector.DefaultDOMNodeHighlighter.prototype = {
@@ -1666,9 +1795,9 @@
highlightDOMNode: function(nodeId, config, objectId)
{
if (objectId || nodeId)
- DOMAgent.highlightNode(config, objectId ? undefined : nodeId, objectId);
+ this._agent.highlightNode(config, objectId ? undefined : nodeId, objectId);
else
- DOMAgent.hideHighlight();
+ this._agent.hideHighlight();
},
/**
@@ -1679,7 +1808,7 @@
*/
setInspectModeEnabled: function(enabled, inspectUAShadowDOM, config, callback)
{
- DOMAgent.setInspectModeEnabled(enabled, inspectUAShadowDOM, config, callback);
+ this._agent.setInspectModeEnabled(enabled, inspectUAShadowDOM, config, callback);
}
}
diff --git a/Source/devtools/front_end/DOMPresentationUtils.js b/Source/devtools/front_end/DOMPresentationUtils.js
index 8bb1905..7102b5a 100644
--- a/Source/devtools/front_end/DOMPresentationUtils.js
+++ b/Source/devtools/front_end/DOMPresentationUtils.js
@@ -90,15 +90,18 @@
container.createChild("span", "webkit-html-attribute-name").textContent = match[3];
}
+/**
+ * @param {!WebInspector.DOMNode} node
+ */
WebInspector.DOMPresentationUtils.linkifyNodeReference = function(node)
{
var link = document.createElement("span");
link.className = "node-link";
WebInspector.DOMPresentationUtils.decorateNodeLabel(node, link);
- link.addEventListener("click", WebInspector.domModel.inspectElement.bind(WebInspector.domModel, node.id), false);
- link.addEventListener("mouseover", WebInspector.domModel.highlightDOMNode.bind(WebInspector.domModel, node.id, "", undefined), false);
- link.addEventListener("mouseout", WebInspector.domModel.hideDOMNodeHighlight.bind(WebInspector.domModel), false);
+ link.addEventListener("click", node.reveal.bind(node), false);
+ link.addEventListener("mouseover", node.highlight.bind(node, undefined, undefined), false);
+ link.addEventListener("mouseout", node.domModel().hideDOMNodeHighlight.bind(node.domModel()), false);
return link;
}
diff --git a/Source/devtools/front_end/DebuggerModel.js b/Source/devtools/front_end/DebuggerModel.js
index 197ce13..abcb8fb 100644
--- a/Source/devtools/front_end/DebuggerModel.js
+++ b/Source/devtools/front_end/DebuggerModel.js
@@ -30,14 +30,15 @@
/**
* @constructor
- * @extends {WebInspector.Object}
+ * @extends {WebInspector.TargetAwareObject}
* @param {!WebInspector.Target} target
*/
WebInspector.DebuggerModel = function(target)
{
+ WebInspector.TargetAwareObject.call(this, target);
+
target.registerDebuggerDispatcher(new WebInspector.DebuggerDispatcher(this));
this._agent = target.debuggerAgent();
- this._target = target;
/** @type {?WebInspector.DebuggerPausedDetails} */
this._debuggerPausedDetails = null;
@@ -55,7 +56,9 @@
this.enableDebugger();
- this.applySkipStackFrameSettings();
+ WebInspector.settings.skipStackFramesSwitch.addChangeListener(this._applySkipStackFrameSettings, this);
+ WebInspector.settings.skipStackFramesPattern.addChangeListener(this._applySkipStackFrameSettings, this);
+ this._applySkipStackFrameSettings();
}
/**
@@ -69,20 +72,6 @@
PauseOnUncaughtExceptions: "uncaught"
};
-/**
- * @constructor
- * @implements {WebInspector.RawLocation}
- * @param {string} scriptId
- * @param {number} lineNumber
- * @param {number} columnNumber
- */
-WebInspector.DebuggerModel.Location = function(scriptId, lineNumber, columnNumber)
-{
- this.scriptId = scriptId;
- this.lineNumber = lineNumber;
- this.columnNumber = columnNumber;
-}
-
WebInspector.DebuggerModel.Events = {
DebuggerWasEnabled: "DebuggerWasEnabled",
DebuggerWasDisabled: "DebuggerWasDisabled",
@@ -191,14 +180,6 @@
this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.DebuggerWasDisabled);
},
- /**
- * @param {!WebInspector.DebuggerModel.Location} rawLocation
- */
- continueToLocation: function(rawLocation)
- {
- this._agent.continueToLocation(rawLocation);
- },
-
stepInto: function()
{
/**
@@ -254,7 +235,7 @@
*/
setBreakpointByScriptLocation: function(rawLocation, condition, callback)
{
- var script = this.scriptForId(rawLocation.scriptId);
+ var script = rawLocation.script();
if (script.sourceURL)
this.setBreakpointByURL(script.sourceURL, rawLocation.lineNumber, rawLocation.columnNumber, condition, callback);
else
@@ -280,6 +261,7 @@
}
columnNumber = Math.max(columnNumber, minColumnNumber);
+ var target = this.target();
/**
* @param {?Protocol.Error} error
* @param {!DebuggerAgent.BreakpointId} breakpointId
@@ -288,7 +270,7 @@
function didSetBreakpoint(error, breakpointId, locations)
{
if (callback) {
- var rawLocations = /** @type {!Array.<!WebInspector.DebuggerModel.Location>} */ (locations);
+ var rawLocations = locations.map(WebInspector.DebuggerModel.Location.fromPayload.bind(WebInspector.DebuggerModel.Location, target));
callback(error ? null : breakpointId, rawLocations);
}
}
@@ -303,6 +285,8 @@
*/
setBreakpointBySourceId: function(rawLocation, condition, callback)
{
+ var target = this.target();
+
/**
* @param {?Protocol.Error} error
* @param {!DebuggerAgent.BreakpointId} breakpointId
@@ -311,11 +295,11 @@
function didSetBreakpoint(error, breakpointId, actualLocation)
{
if (callback) {
- var rawLocation = /** @type {!WebInspector.DebuggerModel.Location} */ (actualLocation);
- callback(error ? null : breakpointId, [rawLocation]);
+ var location = WebInspector.DebuggerModel.Location.fromPayload(target, actualLocation);
+ callback(error ? null : breakpointId, [location]);
}
}
- this._agent.setBreakpoint(rawLocation, condition, didSetBreakpoint);
+ this._agent.setBreakpoint(rawLocation.payload(), condition, didSetBreakpoint);
WebInspector.userMetrics.ScriptsBreakpointSet.record();
},
@@ -345,7 +329,7 @@
*/
_breakpointResolved: function(breakpointId, location)
{
- this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.BreakpointResolved, {breakpointId: breakpointId, location: location});
+ this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.BreakpointResolved, {breakpointId: breakpointId, location: WebInspector.DebuggerModel.Location.fromPayload(this.target(), location)});
},
_globalObjectCleared: function()
@@ -461,7 +445,7 @@
*/
_pausedScript: function(callFrames, reason, auxData, breakpointIds, asyncStackTrace)
{
- this._setDebuggerPausedDetails(new WebInspector.DebuggerPausedDetails(this, callFrames, reason, auxData, breakpointIds, asyncStackTrace));
+ this._setDebuggerPausedDetails(new WebInspector.DebuggerPausedDetails(this.target(), callFrames, reason, auxData, breakpointIds, asyncStackTrace));
},
_resumedScript: function()
@@ -483,7 +467,7 @@
*/
_parsedScriptSource: function(scriptId, sourceURL, startLine, startColumn, endLine, endColumn, isContentScript, sourceMapURL, hasSourceURL)
{
- var script = new WebInspector.Script(scriptId, sourceURL, startLine, startColumn, endLine, endColumn, isContentScript, sourceMapURL, hasSourceURL);
+ var script = new WebInspector.Script(this.target(), scriptId, sourceURL, startLine, startColumn, endLine, endColumn, isContentScript, sourceMapURL, hasSourceURL);
this._registerScript(script);
this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.ParsedScriptSource, script);
},
@@ -515,7 +499,7 @@
{
if (script.sourceURL)
return this.createRawLocationByURL(script.sourceURL, lineNumber, columnNumber)
- return new WebInspector.DebuggerModel.Location(script.scriptId, lineNumber, columnNumber);
+ return new WebInspector.DebuggerModel.Location(this.target(), script.scriptId, lineNumber, columnNumber);
},
/**
@@ -539,7 +523,7 @@
closestScript = script;
break;
}
- return closestScript ? new WebInspector.DebuggerModel.Location(closestScript.scriptId, lineNumber, columnNumber) : null;
+ return closestScript ? new WebInspector.DebuggerModel.Location(this.target(), closestScript.scriptId, lineNumber, columnNumber) : null;
},
/**
@@ -593,7 +577,7 @@
else if (returnByValue)
callback(null, !!wasThrown, wasThrown ? null : result);
else
- callback(WebInspector.RemoteObject.fromPayload(result, this._target), !!wasThrown);
+ callback(this.target().runtimeModel.createRemoteObject(result), !!wasThrown);
if (objectGroup === "console")
this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.ConsoleCommandEvaluatedInSelectedCallFrame);
@@ -625,7 +609,7 @@
for (var i = 0; i < selectedCallFrame.scopeChain.length; ++i) {
var scope = selectedCallFrame.scopeChain[i];
- var object = WebInspector.RemoteObject.fromPayload(scope.object, this._target);
+ var object = this.target().runtimeModel.createRemoteObject(scope.object);
pendingRequests++;
object.getAllProperties(false, propertiesCollected);
}
@@ -689,7 +673,7 @@
this._pausedScript(newCallFrames, this._debuggerPausedDetails.reason, this._debuggerPausedDetails.auxData, this._debuggerPausedDetails.breakpointIds, asyncStackTrace);
},
- applySkipStackFrameSettings: function()
+ _applySkipStackFrameSettings: function()
{
if (!WebInspector.experimentsSettings.frameworksDebuggingSupport.isEnabled())
return;
@@ -698,7 +682,30 @@
this._agent.skipStackFrames(patternParameter);
},
- __proto__: WebInspector.Object.prototype
+ /**
+ * @param {!WebInspector.RemoteObject} remoteObject
+ * @param {function(?DebuggerAgent.FunctionDetails)} callback
+ */
+ functionDetails: function(remoteObject, callback)
+ {
+ this._agent.getFunctionDetails(remoteObject.objectId, didGetDetails);
+
+ /**
+ * @param {?Protocol.Error} error
+ * @param {!DebuggerAgent.FunctionDetails} response
+ */
+ function didGetDetails(error, response)
+ {
+ if (error) {
+ console.error(error);
+ callback(null);
+ return;
+ }
+ callback(response);
+ }
+ },
+
+ __proto__: WebInspector.TargetAwareObject.prototype
}
WebInspector.DebuggerEventTypes = {
@@ -785,41 +792,113 @@
/**
* @constructor
- * @param {!WebInspector.DebuggerModel} debuggerModel
+ * @implements {WebInspector.RawLocation}
+ * @extends {WebInspector.TargetAware}
+ * @param {!WebInspector.Target} target
+ * @param {string} scriptId
+ * @param {number} lineNumber
+ * @param {number=} columnNumber
+ */
+WebInspector.DebuggerModel.Location = function(target, scriptId, lineNumber, columnNumber)
+{
+ WebInspector.TargetAware.call(this, target);
+ this._debuggerModel = target.debuggerModel;
+ this.scriptId = scriptId;
+ this.lineNumber = lineNumber;
+ this.columnNumber = columnNumber;
+}
+
+/**
+ * @param {!WebInspector.Target} target
+ * @param {!DebuggerAgent.Location} payload
+ */
+WebInspector.DebuggerModel.Location.fromPayload = function(target, payload)
+{
+ return new WebInspector.DebuggerModel.Location(target, payload.scriptId, payload.lineNumber, payload.columnNumber);
+}
+
+WebInspector.DebuggerModel.Location.prototype = {
+ /**
+ * @return {!DebuggerAgent.Location}
+ */
+ payload: function()
+ {
+ return { scriptId: this.scriptId, lineNumber: this.lineNumber, columnNumber: this.columnNumber };
+ },
+
+ /**
+ * @return {!WebInspector.Script}
+ */
+ script: function()
+ {
+ return this._debuggerModel.scriptForId(this.scriptId);
+ },
+
+ /**
+ * @param {function(!WebInspector.UILocation):(boolean|undefined)} updateDelegate
+ * @return {!WebInspector.Script.Location}
+ */
+ createLiveLocation: function(updateDelegate)
+ {
+ return this._debuggerModel.createLiveLocation(this, updateDelegate);
+ },
+
+ /**
+ * @return {?WebInspector.UILocation}
+ */
+ toUILocation: function()
+ {
+ return this._debuggerModel.rawLocationToUILocation(this);
+ },
+
+ continueToLocation: function()
+ {
+ this._debuggerModel._agent.continueToLocation(this.payload());
+ },
+
+ __proto__: WebInspector.TargetAware.prototype
+}
+
+/**
+ * @constructor
+ * @extends {WebInspector.TargetAware}
+ * @param {!WebInspector.Target} target
* @param {!WebInspector.Script} script
* @param {!DebuggerAgent.CallFrame} payload
* @param {boolean=} isAsync
*/
-WebInspector.DebuggerModel.CallFrame = function(debuggerModel, script, payload, isAsync)
+WebInspector.DebuggerModel.CallFrame = function(target, script, payload, isAsync)
{
- this._debuggerModel = debuggerModel;
- this._debuggerAgent = debuggerModel._agent;
+ WebInspector.TargetAware.call(this, target);
+ this._debuggerAgent = target.debuggerModel._agent;
this._script = script;
this._payload = payload;
/** @type {!Array.<!WebInspector.Script.Location>} */
- this._locations = [];
+ this._liveLocations = [];
this._isAsync = isAsync;
+ this._location = WebInspector.DebuggerModel.Location.fromPayload(target, payload.location);
}
/**
- * @param {!WebInspector.DebuggerModel} debuggerModel
+ * @param {!WebInspector.Target} target
* @param {!Array.<!DebuggerAgent.CallFrame>} callFrames
* @param {boolean=} isAsync
* @return {!Array.<!WebInspector.DebuggerModel.CallFrame>}
*/
-WebInspector.DebuggerModel.CallFrame.fromPayloadArray = function(debuggerModel, callFrames, isAsync)
+WebInspector.DebuggerModel.CallFrame.fromPayloadArray = function(target, callFrames, isAsync)
{
var result = [];
for (var i = 0; i < callFrames.length; ++i) {
var callFrame = callFrames[i];
- var script = debuggerModel.scriptForId(callFrame.location.scriptId);
+ var script = target.debuggerModel.scriptForId(callFrame.location.scriptId);
if (script)
- result.push(new WebInspector.DebuggerModel.CallFrame(debuggerModel, script, callFrame, isAsync));
+ result.push(new WebInspector.DebuggerModel.CallFrame(target, script, callFrame, isAsync));
}
return result;
}
WebInspector.DebuggerModel.CallFrame.prototype = {
+
/**
* @return {!WebInspector.Script}
*/
@@ -853,19 +932,19 @@
},
/**
- * @return {!RuntimeAgent.RemoteObject}
+ * @return {?WebInspector.RemoteObject}
*/
- get this()
+ thisObject: function()
{
- return this._payload.this;
+ return this._payload.this ? this.target().runtimeModel.createRemoteObject(this._payload.this) : null;
},
/**
- * @return {!RuntimeAgent.RemoteObject|undefined}
+ * @return {?WebInspector.RemoteObject}
*/
- get returnValue()
+ returnValue: function()
{
- return this._payload.returnValue;
+ return this._payload.returnValue ? this.target().runtimeModel.createRemoteObject(this._payload.returnValue) : null
},
/**
@@ -879,10 +958,9 @@
/**
* @return {!WebInspector.DebuggerModel.Location}
*/
- get location()
+ location: function()
{
- var rawLocation = /** @type {!WebInspector.DebuggerModel.Location} */ (this._payload.location);
- return rawLocation;
+ return this._location;
},
/**
@@ -936,7 +1014,7 @@
function protocolCallback(error, callFrames, details, asyncStackTrace)
{
if (!error)
- this._debuggerModel.callStackModified(callFrames, details, asyncStackTrace);
+ this.target().debuggerModel.callStackModified(callFrames, details, asyncStackTrace);
if (callback)
callback(error);
}
@@ -949,17 +1027,19 @@
*/
createLiveLocation: function(updateDelegate)
{
- var location = this._script.createLiveLocation(this.location, updateDelegate);
- this._locations.push(location);
- return location;
+ var liveLocation = this._location.createLiveLocation(updateDelegate);
+ this._liveLocations.push(liveLocation);
+ return liveLocation;
},
dispose: function()
{
- for (var i = 0; i < this._locations.length; ++i)
- this._locations[i].dispose();
- this._locations = [];
- }
+ for (var i = 0; i < this._liveLocations.length; ++i)
+ this._liveLocations[i].dispose();
+ this._liveLocations = [];
+ },
+
+ __proto__: WebInspector.TargetAware.prototype
}
/**
@@ -976,19 +1056,19 @@
}
/**
- * @param {!WebInspector.DebuggerModel} debuggerModel
+ * @param {!WebInspector.Target} target
* @param {!DebuggerAgent.StackTrace=} payload
* @param {boolean=} isAsync
* @return {?WebInspector.DebuggerModel.StackTrace}
*/
-WebInspector.DebuggerModel.StackTrace.fromPayload = function(debuggerModel, payload, isAsync)
+WebInspector.DebuggerModel.StackTrace.fromPayload = function(target, payload, isAsync)
{
if (!payload)
return null;
- var callFrames = WebInspector.DebuggerModel.CallFrame.fromPayloadArray(debuggerModel, payload.callFrames, isAsync);
+ var callFrames = WebInspector.DebuggerModel.CallFrame.fromPayloadArray(target, payload.callFrames, isAsync);
if (!callFrames.length)
return null;
- var asyncStackTrace = WebInspector.DebuggerModel.StackTrace.fromPayload(debuggerModel, payload.asyncStackTrace, true);
+ var asyncStackTrace = WebInspector.DebuggerModel.StackTrace.fromPayload(target, payload.asyncStackTrace, true);
return new WebInspector.DebuggerModel.StackTrace(callFrames, asyncStackTrace, payload.description);
}
@@ -1004,30 +1084,44 @@
/**
* @constructor
- * @param {!WebInspector.DebuggerModel} debuggerModel
+ * @extends {WebInspector.TargetAware}
+ * @param {!WebInspector.Target} target
* @param {!Array.<!DebuggerAgent.CallFrame>} callFrames
* @param {string} reason
* @param {!Object|undefined} auxData
* @param {!Array.<string>} breakpointIds
* @param {!DebuggerAgent.StackTrace=} asyncStackTrace
*/
-WebInspector.DebuggerPausedDetails = function(debuggerModel, callFrames, reason, auxData, breakpointIds, asyncStackTrace)
+WebInspector.DebuggerPausedDetails = function(target, callFrames, reason, auxData, breakpointIds, asyncStackTrace)
{
- this.callFrames = WebInspector.DebuggerModel.CallFrame.fromPayloadArray(debuggerModel, callFrames);
+ WebInspector.TargetAware.call(this, target);
+ this.callFrames = WebInspector.DebuggerModel.CallFrame.fromPayloadArray(target, callFrames);
this.reason = reason;
this.auxData = auxData;
this.breakpointIds = breakpointIds;
- this.asyncStackTrace = WebInspector.DebuggerModel.StackTrace.fromPayload(debuggerModel, asyncStackTrace, true);
+ this.asyncStackTrace = WebInspector.DebuggerModel.StackTrace.fromPayload(target, asyncStackTrace, true);
}
WebInspector.DebuggerPausedDetails.prototype = {
+ /**
+ * @return {?WebInspector.RemoteObject}
+ */
+ exception: function()
+ {
+ if (this.reason !== WebInspector.DebuggerModel.BreakReason.Exception)
+ return null;
+ return this.target().runtimeModel.createRemoteObject(/** @type {!RuntimeAgent.RemoteObject} */(this.auxData));
+ },
+
dispose: function()
{
for (var i = 0; i < this.callFrames.length; ++i)
this.callFrames[i].dispose();
if (this.asyncStackTrace)
this.asyncStackTrace.dispose();
- }
+ },
+
+ __proto__: WebInspector.TargetAware.prototype
}
/**
diff --git a/Source/devtools/front_end/DefaultScriptMapping.js b/Source/devtools/front_end/DefaultScriptMapping.js
index 3019a5c..4186fa6 100644
--- a/Source/devtools/front_end/DefaultScriptMapping.js
+++ b/Source/devtools/front_end/DefaultScriptMapping.js
@@ -52,7 +52,7 @@
rawLocationToUILocation: function(rawLocation)
{
var debuggerModelLocation = /** @type {!WebInspector.DebuggerModel.Location} */ (rawLocation);
- var script = this._debuggerModel.scriptForId(debuggerModelLocation.scriptId);
+ var script = debuggerModelLocation.script();
var uiSourceCode = this._uiSourceCodeForScriptId[script.scriptId];
var lineNumber = debuggerModelLocation.lineNumber;
var columnNumber = debuggerModelLocation.columnNumber || 0;
diff --git a/Source/devtools/front_end/ElementsPanel.js b/Source/devtools/front_end/ElementsPanel.js
index 0c64595..94ae149 100644
--- a/Source/devtools/front_end/ElementsPanel.js
+++ b/Source/devtools/front_end/ElementsPanel.js
@@ -41,6 +41,7 @@
/**
* @constructor
* @implements {WebInspector.Searchable}
+ * @implements {WebInspector.TargetManager.Observer}
* @extends {WebInspector.Panel}
*/
WebInspector.ElementsPanel = function()
@@ -71,12 +72,6 @@
this.contentElement.addEventListener("contextmenu", this._contextMenuEventFired.bind(this), true);
this._splitView.sidebarElement().addEventListener("contextmenu", this._sidebarContextMenuEventFired.bind(this), false);
- this.treeOutline = new WebInspector.ElementsTreeOutline(true, true, this._populateContextMenu.bind(this), this._setPseudoClassForNodeId.bind(this));
- this.treeOutline.wireToDomAgent();
-
- this.treeOutline.addEventListener(WebInspector.ElementsTreeOutline.Events.SelectedNodeChanged, this._selectedNodeChanged, this);
- this.treeOutline.addEventListener(WebInspector.ElementsTreeOutline.Events.ElementsTreeUpdated, this._updateBreadcrumbIfNeeded, this);
-
var crumbsContainer = stackElement.createChild("div");
crumbsContainer.id = "elements-crumbs";
this.crumbsElement = crumbsContainer.createChild("div", "crumbs");
@@ -86,7 +81,7 @@
this.sidebarPanes = {};
this.sidebarPanes.platformFonts = new WebInspector.PlatformFontsSidebarPane();
this.sidebarPanes.computedStyle = new WebInspector.ComputedStyleSidebarPane();
- this.sidebarPanes.styles = new WebInspector.StylesSidebarPane(this.sidebarPanes.computedStyle, this._setPseudoClassForNodeId.bind(this));
+ this.sidebarPanes.styles = new WebInspector.StylesSidebarPane(this.sidebarPanes.computedStyle, this._setPseudoClassForNode.bind(this));
this._matchedStylesFilterBoxContainer = document.createElement("div");
this._matchedStylesFilterBoxContainer.className = "sidebar-pane-filter-box";
@@ -117,23 +112,65 @@
this._popoverHelper = new WebInspector.PopoverHelper(this.element, this._getPopoverAnchor.bind(this), this._showPopover.bind(this));
this._popoverHelper.setTimeout(0);
- WebInspector.domModel.addEventListener(WebInspector.DOMModel.Events.DocumentUpdated, this._documentUpdatedEvent, this);
+ /** @type {!Array.<!WebInspector.ElementsTreeOutline>} */
+ this._treeOutlines = [];
+ /** @type {!Map.<!WebInspector.Target, !WebInspector.ElementsTreeOutline>} */
+ this._targetToTreeOutline = new Map();
+ WebInspector.targetManager.observeTargets(this);
WebInspector.settings.showUAShadowDOM.addChangeListener(this._showUAShadowDOMChanged.bind(this));
-
- WebInspector.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.ModelWasEnabled, this._updateSidebars, this);
}
WebInspector.ElementsPanel.prototype = {
+ /**
+ * @param {!WebInspector.Target} target
+ */
+ targetAdded: function(target)
+ {
+ var treeOutline = new WebInspector.ElementsTreeOutline(target, true, true, this._populateContextMenu.bind(this), this._setPseudoClassForNode.bind(this));
+ treeOutline.wireToDOMModel();
+ treeOutline.addEventListener(WebInspector.ElementsTreeOutline.Events.SelectedNodeChanged, this._selectedNodeChanged, this);
+ treeOutline.addEventListener(WebInspector.ElementsTreeOutline.Events.ElementsTreeUpdated, this._updateBreadcrumbIfNeeded, this);
+ this._treeOutlines.push(treeOutline);
+ this._targetToTreeOutline.put(target, treeOutline);
+
+ target.domModel.addEventListener(WebInspector.DOMModel.Events.DocumentUpdated, this._documentUpdatedEvent.bind(this, target));
+ target.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.ModelWasEnabled, this._updateSidebars, this);
+
+ // Perform attach if necessary.
+ if (this.isShowing())
+ this.wasShown();
+ },
+
+ /**
+ * @param {!WebInspector.Target} target
+ */
+ targetRemoved: function(target) { },
+
+ /**
+ * @param {?WebInspector.Target} target
+ */
+ activeTargetChanged: function(target) { },
+
+ /**
+ * @return {?WebInspector.ElementsTreeOutline}
+ */
+ _firstTreeOutlineDeprecated: function()
+ {
+ return this._treeOutlines[0] || null;
+ },
+
_updateTreeOutlineVisibleWidth: function()
{
- if (!this.treeOutline)
+ if (!this._treeOutlines.length)
return;
var width = this._splitView.element.offsetWidth;
if (this._splitView.isVertical())
width -= this._splitView.sidebarSize();
- this.treeOutline.setVisibleWidth(width);
- this.treeOutline.updateSelection();
+ for (var i = 0; i > this._treeOutlines.length; ++i) {
+ this._treeOutlines[i].setVisibleWidth(width);
+ this._treeOutlines[i].updateSelection();
+ }
this.updateBreadcrumbSizes();
},
@@ -142,7 +179,7 @@
*/
defaultFocusedElement: function()
{
- return this.treeOutline.element;
+ return this._treeOutlines.length ? this._treeOutlines[0].element : this.element;
},
/**
@@ -155,32 +192,39 @@
wasShown: function()
{
- // Attach heavy component lazily
- if (this.treeOutline.element.parentElement !== this.contentElement)
- this.contentElement.appendChild(this.treeOutline.element);
-
+ for (var i = 0; i < this._treeOutlines.length; ++i) {
+ var treeOutline = this._treeOutlines[i];
+ // Attach heavy component lazily
+ if (treeOutline.element.parentElement !== this.contentElement)
+ this.contentElement.appendChild(treeOutline.element);
+ }
WebInspector.Panel.prototype.wasShown.call(this);
-
this.updateBreadcrumb();
- this.treeOutline.updateSelection();
- this.treeOutline.setVisible(true);
- if (!this.treeOutline.rootDOMNode)
- if (WebInspector.domModel.existingDocument())
- this._documentUpdated(WebInspector.domModel.existingDocument());
- else
- WebInspector.domModel.requestDocument();
+ for (var i = 0; i < this._treeOutlines.length; ++i) {
+ var treeOutline = this._treeOutlines[i];
+ treeOutline.updateSelection();
+ treeOutline.setVisible(true);
+
+ if (!treeOutline.rootDOMNode)
+ if (treeOutline.domModel().existingDocument())
+ this._documentUpdated(treeOutline.target(), treeOutline.domModel().existingDocument());
+ else
+ treeOutline.domModel().requestDocument();
+ }
+
},
willHide: function()
{
- WebInspector.domModel.hideDOMNodeHighlight();
- this.treeOutline.setVisible(false);
+ for (var i = 0; i < this._treeOutlines.length; ++i) {
+ var treeOutline = this._treeOutlines[i];
+ treeOutline.domModel().hideDOMNodeHighlight();
+ treeOutline.setVisible(false);
+ // Detach heavy component on hide
+ this.contentElement.removeChild(treeOutline.element);
+ }
this._popoverHelper.hidePopover();
-
- // Detach heavy component on hide
- this.contentElement.removeChild(this.treeOutline.element);
-
WebInspector.Panel.prototype.willHide.call(this);
},
@@ -200,33 +244,16 @@
},
/**
- * @param {!DOMAgent.NodeId} nodeId
+ * @param {!WebInspector.DOMNode} node
* @param {string} pseudoClass
* @param {boolean} enable
*/
- _setPseudoClassForNodeId: function(nodeId, pseudoClass, enable)
+ _setPseudoClassForNode: function(node, pseudoClass, enable)
{
- var node = WebInspector.domModel.nodeForId(nodeId);
- if (!node)
+ if (!node || !node.target().cssModel.forcePseudoState(node, pseudoClass, enable))
return;
- var pseudoClasses = node.getUserProperty(WebInspector.ElementsTreeOutline.PseudoStateDecorator.PropertyName);
- if (enable) {
- pseudoClasses = pseudoClasses || [];
- if (pseudoClasses.indexOf(pseudoClass) >= 0)
- return;
- pseudoClasses.push(pseudoClass);
- node.setUserProperty(WebInspector.ElementsTreeOutline.PseudoStateDecorator.PropertyName, pseudoClasses);
- } else {
- if (!pseudoClasses || pseudoClasses.indexOf(pseudoClass) < 0)
- return;
- pseudoClasses.remove(pseudoClass);
- if (!pseudoClasses.length)
- node.removeUserProperty(WebInspector.ElementsTreeOutline.PseudoStateDecorator.PropertyName);
- }
-
- this.treeOutline.updateOpenCloseTags(node);
- WebInspector.cssModel.forcePseudoState(node.id, node.getUserProperty(WebInspector.ElementsTreeOutline.PseudoStateDecorator.PropertyName));
+ this._targetToTreeOutline.get(node.target()).updateOpenCloseTags(node);
this._metricsPaneEdited();
this._stylesPaneEdited();
@@ -272,21 +299,30 @@
delete this.currentQuery;
},
- _documentUpdatedEvent: function(event)
+ /**
+ * @param {!WebInspector.Target} target
+ * @param {!WebInspector.Event} event
+ */
+ _documentUpdatedEvent: function(target, event)
{
- this._documentUpdated(event.data);
+ this._documentUpdated(target, /** @type {?WebInspector.DOMDocument} */ (event.data));
},
- _documentUpdated: function(inspectedRootDocument)
+ /**
+ * @param {!WebInspector.Target} target
+ * @param {?WebInspector.DOMDocument} inspectedRootDocument
+ */
+ _documentUpdated: function(target, inspectedRootDocument)
{
this._reset();
this.searchCanceled();
- this.treeOutline.rootDOMNode = inspectedRootDocument;
+ var treeOutline = this._targetToTreeOutline.get(target);
+ treeOutline.rootDOMNode = inspectedRootDocument;
if (!inspectedRootDocument) {
if (this.isShowing())
- WebInspector.domModel.requestDocument();
+ target.domModel.requestDocument();
return;
}
@@ -305,8 +341,8 @@
return;
this.selectDOMNode(candidateFocusNode);
- if (this.treeOutline.selectedTreeElement)
- this.treeOutline.selectedTreeElement.expand();
+ if (treeOutline.selectedTreeElement)
+ treeOutline.selectedTreeElement.expand();
}
/**
@@ -319,7 +355,7 @@
// Focused node has been explicitly set while reaching out for the last selected node.
return;
}
- var node = nodeId ? WebInspector.domModel.nodeForId(nodeId) : null;
+ var node = nodeId ? target.domModel.nodeForId(nodeId) : null;
selectNode.call(this, node);
}
@@ -327,7 +363,7 @@
return;
if (this._selectedPathOnReset)
- WebInspector.domModel.pushNodeByPathToFrontend(this._selectedPathOnReset, selectLastSelectedNode.bind(this));
+ target.domModel.pushNodeByPathToFrontend(this._selectedPathOnReset, selectLastSelectedNode.bind(this));
else
selectNode.call(this, null);
delete this._selectedPathOnReset;
@@ -381,7 +417,8 @@
_contextMenuEventFired: function(event)
{
var contextMenu = new WebInspector.ContextMenu(event);
- this.treeOutline.populateContextMenu(contextMenu, event);
+ for (var i = 0; i < this._treeOutlines.length; ++i)
+ this._treeOutlines[i].populateContextMenu(contextMenu, event);
contextMenu.show();
},
@@ -396,7 +433,8 @@
if (!selectedNode)
return;
- var treeElement = this.treeOutline.findTreeElement(selectedNode);
+ var treeOutline = this._targetToTreeOutline.get(selectedNode.target());
+ var treeElement = treeOutline.findTreeElement(selectedNode);
if (treeElement)
treeElement.updateSelection(); // Recalculate selection highlight dimensions.
},
@@ -436,7 +474,7 @@
_loadDimensionsForNode: function(treeElement, callback)
{
// We get here for CSS properties, too, so bail out early for non-DOM treeElements.
- if (treeElement.treeOutline !== this.treeOutline) {
+ if (!(treeElement.treeOutline instanceof WebInspector.ElementsTreeOutline)) {
callback();
return;
}
@@ -448,7 +486,7 @@
return;
}
- WebInspector.RemoteObject.resolveNode(node, "", resolvedNode);
+ node.resolveToObject("", resolvedNode);
function resolvedNode(object)
{
@@ -522,6 +560,10 @@
_highlightCurrentSearchResult: function()
{
+ var treeOutline = this._firstTreeOutlineDeprecated();
+ if (!treeOutline)
+ return;
+
var index = this._currentSearchResultIndex;
var searchResults = this._searchResults;
var searchResult = searchResults[index];
@@ -549,7 +591,7 @@
this._searchableView.updateCurrentMatchIndex(index);
- var treeElement = this.treeOutline.findTreeElement(searchResult);
+ var treeElement = treeOutline.findTreeElement(searchResult);
if (treeElement) {
treeElement.highlightSearchResults(this._searchQuery);
treeElement.reveal();
@@ -566,7 +608,8 @@
var searchResult = this._searchResults[this._currentSearchResultIndex];
if (!searchResult)
return;
- var treeElement = this.treeOutline.findTreeElement(searchResult);
+ var treeOutline = this._targetToTreeOutline.get(searchResult.target());
+ var treeElement = treeOutline.findTreeElement(searchResult);
if (treeElement)
treeElement.hideSearchHighlights();
},
@@ -576,7 +619,12 @@
*/
selectedDOMNode: function()
{
- return this.treeOutline.selectedDOMNode();
+ for (var i = 0; i < this._treeOutlines.length; ++i) {
+ var treeOutline = this._treeOutlines[i];
+ if (treeOutline.selectedDOMNode())
+ return treeOutline.selectedDOMNode();
+ }
+ return null;
},
/**
@@ -584,7 +632,13 @@
*/
selectDOMNode: function(node, focus)
{
- this.treeOutline.selectDOMNode(node, focus);
+ for (var i = 0; i < this._treeOutlines.length; ++i) {
+ var treeOutline = this._treeOutlines[i];
+ if (treeOutline.target() === node.target())
+ treeOutline.selectDOMNode(node, focus);
+ else
+ treeOutline.selectDOMNode(null);
+ }
},
/**
@@ -625,13 +679,9 @@
{
var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY);
var crumbElement = nodeUnderMouse.enclosingNodeOrSelfWithClass("crumb");
-
- WebInspector.domModel.highlightDOMNode(crumbElement ? crumbElement.representedObject.id : 0);
-
- if ("_mouseOutOfCrumbsTimeout" in this) {
- clearTimeout(this._mouseOutOfCrumbsTimeout);
- delete this._mouseOutOfCrumbsTimeout;
- }
+ var node = /** @type {?WebInspector.DOMNode} */ (crumbElement ? crumbElement.representedObject : null);
+ if (node)
+ node.highlight();
},
_mouseMovedOutOfCrumbs: function(event)
@@ -640,9 +690,8 @@
if (nodeUnderMouse && nodeUnderMouse.isDescendant(this.crumbsElement))
return;
- WebInspector.domModel.hideDOMNodeHighlight();
-
- this._mouseOutOfCrumbsTimeout = setTimeout(this.updateBreadcrumbSizes.bind(this), 1000);
+ for (var i = 0; i < this._treeOutlines.length; ++i)
+ this._treeOutlines[i].domModel().hideDOMNodeHighlight();
},
/**
@@ -1002,11 +1051,21 @@
},
/**
+ * @return {boolean}
+ */
+ _cssModelEnabledForSelectedNode: function()
+ {
+ if (!this.selectedDOMNode())
+ return true;
+ return this.selectedDOMNode().target().cssModel.isEnabled();
+ },
+
+ /**
* @param {boolean=} forceUpdate
*/
updateStyles: function(forceUpdate)
{
- if (!WebInspector.cssModel.isEnabled())
+ if (!this._cssModelEnabledForSelectedNode())
return;
var stylesSidebarPane = this.sidebarPanes.styles;
var computedStylePane = this.sidebarPanes.computedStyle;
@@ -1019,7 +1078,7 @@
updateMetrics: function()
{
- if (!WebInspector.cssModel.isEnabled())
+ if (!this._cssModelEnabledForSelectedNode())
return;
var metricsSidebarPane = this.sidebarPanes.metrics;
if (!metricsSidebarPane.isShowing() || !metricsSidebarPane.needsUpdate)
@@ -1031,7 +1090,7 @@
updatePlatformFonts: function()
{
- if (!WebInspector.cssModel.isEnabled())
+ if (!this._cssModelEnabledForSelectedNode())
return;
var platformFontsSidebar = this.sidebarPanes.platformFonts;
if (!platformFontsSidebar.isShowing() || !platformFontsSidebar.needsUpdate)
@@ -1080,18 +1139,22 @@
var isRedoKey = WebInspector.isMac() ? event.metaKey && event.shiftKey && event.keyIdentifier === "U+005A" : // Z key
event.ctrlKey && event.keyIdentifier === "U+0059"; // Y key
if (isRedoKey) {
- DOMAgent.redo(this._updateSidebars.bind(this));
+ WebInspector.domModel.redo(this._updateSidebars.bind(this));
event.handled = true;
}
}
- if (!this.treeOutline.editing()) {
+ var treeOutline = this._firstTreeOutlineDeprecated();
+ if (!treeOutline)
+ return;
+
+ if (!treeOutline.editing()) {
handleUndoRedo.call(this);
if (event.handled)
return;
}
- this.treeOutline.handleShortcut(event);
+ treeOutline.handleShortcut(event);
},
handleCopyEvent: function(event)
@@ -1119,34 +1182,30 @@
},
/**
- * @param {!DOMAgent.NodeId} nodeId
+ * @param {!WebInspector.DOMNode} node
*/
- revealAndSelectNode: function(nodeId)
+ revealAndSelectNode: function(node)
{
WebInspector.inspectorView.setCurrentPanel(this);
-
- var node = WebInspector.domModel.nodeForId(nodeId);
- if (!node)
- return;
-
node = WebInspector.settings.showUAShadowDOM.get() ? node : this._leaveUserAgentShadowDOM(node);
- WebInspector.domModel.highlightDOMNodeForTwoSeconds(nodeId);
+ node.highlightForTwoSeconds();
this.selectDOMNode(node, true);
},
/**
* @param {!WebInspector.ContextMenu} contextMenu
- * @param {!Object} target
+ * @param {!Object} object
*/
- appendApplicableItems: function(event, contextMenu, target)
+ appendApplicableItems: function(event, contextMenu, object)
{
/**
+ * @param {!WebInspector.Target} target
* @param {?DOMAgent.NodeId} nodeId
*/
- function selectNode(nodeId)
+ function selectNode(target, nodeId)
{
if (nodeId)
- WebInspector.domModel.inspectElement(nodeId);
+ target.domModel.inspectElement(nodeId);
}
/**
@@ -1154,23 +1213,22 @@
*/
function revealElement(remoteObject)
{
- remoteObject.pushNodeToFrontend(selectNode);
+ remoteObject.pushNodeToFrontend(selectNode.bind(null, /** @type {!WebInspector.Target} */ (remoteObject.target())));
}
var commandCallback;
- if (target instanceof WebInspector.RemoteObject) {
- var remoteObject = /** @type {!WebInspector.RemoteObject} */ (target);
+ if (object instanceof WebInspector.RemoteObject) {
+ var remoteObject = /** @type {!WebInspector.RemoteObject} */ (object);
if (remoteObject.subtype === "node")
commandCallback = revealElement.bind(null, remoteObject);
- } else if (target instanceof WebInspector.DOMNode) {
- var domNode = /** @type {!WebInspector.DOMNode} */ (target);
- if (domNode.id)
- commandCallback = WebInspector.domModel.inspectElement.bind(WebInspector.domModel, domNode.id);
+ } else if (object instanceof WebInspector.DOMNode) {
+ var domNode = /** @type {!WebInspector.DOMNode} */ (object);
+ commandCallback = domNode.reveal.bind(domNode);
}
if (!commandCallback)
return;
// Skip adding "Reveal..." menu item for our own tree outline.
- if (this.treeOutline.element.isAncestor(event.target))
+ if (this.element.isAncestor(event.target))
return;
contextMenu.appendItem(WebInspector.useLowerCaseMenuTitles() ? "Reveal in Elements panel" : "Reveal in Elements Panel", commandCallback);
},
@@ -1189,7 +1247,8 @@
_showUAShadowDOMChanged: function()
{
- this.treeOutline.update();
+ for (var i = 0; i < this._treeOutlines.length; ++i)
+ this._treeOutlines[i].update();
},
/**
@@ -1366,6 +1425,6 @@
WebInspector.inspectElementModeController.disable();
}
- /** @type {!WebInspector.ElementsPanel} */ (WebInspector.inspectorView.panel("elements")).revealAndSelectNode(node.id);
+ /** @type {!WebInspector.ElementsPanel} */ (WebInspector.inspectorView.panel("elements")).revealAndSelectNode(node);
}
}
diff --git a/Source/devtools/front_end/ElementsTreeOutline.js b/Source/devtools/front_end/ElementsTreeOutline.js
index f3ce135..c50dc58 100644
--- a/Source/devtools/front_end/ElementsTreeOutline.js
+++ b/Source/devtools/front_end/ElementsTreeOutline.js
@@ -31,13 +31,16 @@
/**
* @constructor
* @extends {TreeOutline}
+ * @param {!WebInspector.Target} target
* @param {boolean=} omitRootDOMNode
* @param {boolean=} selectEnabled
* @param {function(!WebInspector.ContextMenu, !WebInspector.DOMNode)=} contextMenuCallback
- * @param {function(!DOMAgent.NodeId, string, boolean)=} setPseudoClassCallback
+ * @param {function(!WebInspector.DOMNode, string, boolean)=} setPseudoClassCallback
*/
-WebInspector.ElementsTreeOutline = function(omitRootDOMNode, selectEnabled, contextMenuCallback, setPseudoClassCallback)
+WebInspector.ElementsTreeOutline = function(target, omitRootDOMNode, selectEnabled, contextMenuCallback, setPseudoClassCallback)
{
+ this._target = target;
+ this._domModel = target.domModel;
this.element = document.createElement("ol");
this.element.className = "elements-tree-outline";
this.element.addEventListener("mousedown", this._onmousedown.bind(this), false);
@@ -100,6 +103,22 @@
WebInspector.ElementsTreeOutline.prototype = {
/**
+ * @return {!WebInspector.Target}
+ */
+ target: function()
+ {
+ return this._target;
+ },
+
+ /**
+ * @return {!WebInspector.DOMModel}
+ */
+ domModel: function()
+ {
+ return this._domModel;
+ },
+
+ /**
* @param {number} width
*/
setVisibleWidth: function(width)
@@ -115,9 +134,9 @@
this._nodeDecorators.push(new WebInspector.ElementsTreeOutline.PseudoStateDecorator());
},
- wireToDomAgent: function()
+ wireToDOMModel: function()
{
- this._elementsTreeUpdater = new WebInspector.ElementsTreeUpdater(this);
+ this._elementsTreeUpdater = new WebInspector.ElementsTreeUpdater(this._target.domModel, this);
},
/**
@@ -397,7 +416,10 @@
this._previousHoveredElement = element;
}
- WebInspector.domModel.highlightDOMNode(element && element._node ? element._node.id : 0);
+ if (element && element._node)
+ element._node.highlight();
+ else
+ this._domModel.hideDOMNodeHighlight();
},
_onmouseout: function(event)
@@ -411,7 +433,7 @@
delete this._previousHoveredElement;
}
- WebInspector.domModel.hideDOMNodeHighlight();
+ this._domModel.hideDOMNodeHighlight();
},
_ondragstart: function(event)
@@ -435,7 +457,7 @@
event.dataTransfer.effectAllowed = "copyMove";
this._treeElementBeingDragged = treeElement;
- WebInspector.domModel.hideDOMNodeHighlight();
+ this._domModel.hideDOMNodeHighlight();
return true;
},
@@ -666,7 +688,7 @@
// Select it and expand if necessary. We force tree update so that it processes dom events and is up to date.
this._updateModifiedNodes();
- var newNode = nodeId ? WebInspector.domModel.nodeForId(nodeId) : null;
+ var newNode = nodeId ? this._domModel.nodeForId(nodeId) : null;
if (!newNode)
return;
@@ -731,7 +753,7 @@
object.release();
}
- WebInspector.RemoteObject.resolveNode(effectiveNode, "", resolvedNode);
+ effectiveNode.resolveToObject("", resolvedNode);
},
__proto__: TreeOutline.prototype
@@ -771,8 +793,6 @@
WebInspector.ElementsTreeOutline.ElementDecorator.call(this);
}
-WebInspector.ElementsTreeOutline.PseudoStateDecorator.PropertyName = "pseudoState";
-
WebInspector.ElementsTreeOutline.PseudoStateDecorator.prototype = {
/**
* @param {!WebInspector.DOMNode} node
@@ -782,7 +802,7 @@
{
if (node.nodeType() !== Node.ELEMENT_NODE)
return null;
- var propertyValue = node.getUserProperty(WebInspector.ElementsTreeOutline.PseudoStateDecorator.PropertyName);
+ var propertyValue = node.getUserProperty(WebInspector.CSSStyleModel.PseudoStatePropertyName);
if (!propertyValue)
return null;
return WebInspector.UIString("Element state: %s", ":" + propertyValue.join(", :"));
@@ -797,7 +817,7 @@
if (node.nodeType() !== Node.ELEMENT_NODE)
return null;
- var descendantCount = node.descendantUserPropertyCount(WebInspector.ElementsTreeOutline.PseudoStateDecorator.PropertyName);
+ var descendantCount = node.descendantUserPropertyCount(WebInspector.CSSStyleModel.PseudoStatePropertyName);
if (!descendantCount)
return null;
if (descendantCount === 1)
@@ -1252,7 +1272,7 @@
this.treeOutline.suppressRevealAndSelect = true;
this.treeOutline.selectDOMNode(this._node, selectedByUser);
if (selectedByUser)
- WebInspector.domModel.highlightDOMNode(this._node.id);
+ this._node.highlight();
this.updateSelection();
this.treeOutline.suppressRevealAndSelect = false;
return true;
@@ -1411,13 +1431,14 @@
var forcedPseudoState = (node ? node.getUserProperty("pseudoState") : null) || [];
for (var i = 0; i < pseudoClasses.length; ++i) {
var pseudoClassForced = forcedPseudoState.indexOf(pseudoClasses[i]) >= 0;
- subMenu.appendCheckboxItem(":" + pseudoClasses[i], this.treeOutline._setPseudoClassCallback.bind(null, node.id, pseudoClasses[i], !pseudoClassForced), pseudoClassForced, false);
+ subMenu.appendCheckboxItem(":" + pseudoClasses[i], this.treeOutline._setPseudoClassCallback.bind(null, node, pseudoClasses[i], !pseudoClassForced), pseudoClassForced, false);
}
},
_populateTextContextMenu: function(contextMenu, textNode)
{
- contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Edit text" : "Edit Text"), this._startEditingTextNode.bind(this, textNode));
+ if (!this._editing)
+ contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Edit text" : "Edit Text"), this._startEditingTextNode.bind(this, textNode));
this._populateNodeContextMenu(contextMenu);
},
@@ -1426,7 +1447,7 @@
// Add free-form node-related actions.
var openTagElement = this.treeOutline.getCachedTreeElement(this.representedObject) || this;
var isEditable = this.hasEditableNode();
- if (isEditable)
+ if (isEditable && !this._editing)
contextMenu.appendItem(WebInspector.UIString("Edit as HTML"), openTagElement._editAsHTML.bind(openTagElement));
var isShadowRoot = this.representedObject.isShadowRoot();
if (!isShadowRoot)
@@ -2376,7 +2397,7 @@
object.callFunction(scrollIntoView);
}
- WebInspector.RemoteObject.resolveNode(this._node, "", scrollIntoViewCallback);
+ this._node.resolveToObject("", scrollIntoViewCallback);
},
/**
@@ -2438,18 +2459,20 @@
/**
* @constructor
+ * @param {!WebInspector.DOMModel} domModel
* @param {!WebInspector.ElementsTreeOutline} treeOutline
*/
-WebInspector.ElementsTreeUpdater = function(treeOutline)
+WebInspector.ElementsTreeUpdater = function(domModel, treeOutline)
{
- WebInspector.domModel.addEventListener(WebInspector.DOMModel.Events.NodeInserted, this._nodeInserted, this);
- WebInspector.domModel.addEventListener(WebInspector.DOMModel.Events.NodeRemoved, this._nodeRemoved, this);
- WebInspector.domModel.addEventListener(WebInspector.DOMModel.Events.AttrModified, this._attributesUpdated, this);
- WebInspector.domModel.addEventListener(WebInspector.DOMModel.Events.AttrRemoved, this._attributesUpdated, this);
- WebInspector.domModel.addEventListener(WebInspector.DOMModel.Events.CharacterDataModified, this._characterDataModified, this);
- WebInspector.domModel.addEventListener(WebInspector.DOMModel.Events.DocumentUpdated, this._documentUpdated, this);
- WebInspector.domModel.addEventListener(WebInspector.DOMModel.Events.ChildNodeCountUpdated, this._childNodeCountUpdated, this);
+ domModel.addEventListener(WebInspector.DOMModel.Events.NodeInserted, this._nodeInserted, this);
+ domModel.addEventListener(WebInspector.DOMModel.Events.NodeRemoved, this._nodeRemoved, this);
+ domModel.addEventListener(WebInspector.DOMModel.Events.AttrModified, this._attributesUpdated, this);
+ domModel.addEventListener(WebInspector.DOMModel.Events.AttrRemoved, this._attributesUpdated, this);
+ domModel.addEventListener(WebInspector.DOMModel.Events.CharacterDataModified, this._characterDataModified, this);
+ domModel.addEventListener(WebInspector.DOMModel.Events.DocumentUpdated, this._documentUpdated, this);
+ domModel.addEventListener(WebInspector.DOMModel.Events.ChildNodeCountUpdated, this._childNodeCountUpdated, this);
+ this._domModel = domModel;
this._treeOutline = treeOutline;
/** @type {!Map.<!WebInspector.DOMNode, !WebInspector.ElementsTreeUpdater.UpdateEntry>} */
this._recentlyModifiedNodes = new Map();
@@ -2589,7 +2612,7 @@
{
this._treeOutline.rootDOMNode = null;
this._treeOutline.selectDOMNode(null, false);
- WebInspector.domModel.hideDOMNodeHighlight();
+ this._domModel.hideDOMNodeHighlight();
this._recentlyModifiedNodes.clear();
}
}
@@ -2623,8 +2646,9 @@
{
if (!(object instanceof WebInspector.DOMNode))
return null;
- var treeOutline = new WebInspector.ElementsTreeOutline(false, false);
- treeOutline.rootDOMNode = /** @type {!WebInspector.DOMNode} */ (object);
+ var node = /** @type {!WebInspector.DOMNode} */ (object);
+ var treeOutline = new WebInspector.ElementsTreeOutline(node.target(), false, false);
+ treeOutline.rootDOMNode = node;
treeOutline.element.classList.add("outline-disclosure");
if (!treeOutline.children[0].hasChildren)
treeOutline.element.classList.add("single-node");
diff --git a/Source/devtools/front_end/EventListenersSidebarPane.js b/Source/devtools/front_end/EventListenersSidebarPane.js
index c725dc0..a639a53 100644
--- a/Source/devtools/front_end/EventListenersSidebarPane.js
+++ b/Source/devtools/front_end/EventListenersSidebarPane.js
@@ -73,6 +73,9 @@
WebInspector.EventListenersSidebarPane._objectGroupName = "event-listeners-sidebar-pane";
WebInspector.EventListenersSidebarPane.prototype = {
+ /**
+ * @param {?WebInspector.DOMNode} node
+ */
update: function(node)
{
RuntimeAgent.releaseObjectGroup(WebInspector.EventListenersSidebarPane._objectGroupName);
@@ -83,8 +86,12 @@
this.sections = [];
var self = this;
- function callback(error, eventListeners) {
- if (error)
+ /**
+ * @param {?Array.<!WebInspector.DOMModel.EventListener>} eventListeners
+ */
+ function callback(eventListeners)
+ {
+ if (!eventListeners)
return;
var selectedNodeOnly = "selected" === WebInspector.settings.eventListenersFilter.get();
@@ -92,13 +99,11 @@
var sectionMap = {};
for (var i = 0; i < eventListeners.length; ++i) {
var eventListener = eventListeners[i];
- if (selectedNodeOnly && (node.id !== eventListener.nodeId))
+ if (selectedNodeOnly && (node.id !== eventListener.payload().nodeId))
continue;
- eventListener.node = WebInspector.domModel.nodeForId(eventListener.nodeId);
- delete eventListener.nodeId; // no longer needed
- if (/^function _inspectorCommandLineAPI_logEvent\(/.test(eventListener.handlerBody.toString()))
+ if (/^function _inspectorCommandLineAPI_logEvent\(/.test(eventListener.payload().handlerBody.toString()))
continue; // ignore event listeners generated by monitorEvent
- var type = eventListener.type;
+ var type = eventListener.payload().type;
var section = sectionMap[type];
if (!section) {
section = new WebInspector.EventListenersSection(type, node.id, self._linkifier);
@@ -173,6 +178,9 @@
}
WebInspector.EventListenersSection.prototype = {
+ /**
+ * @param {!WebInspector.DOMModel.EventListener} eventListener
+ */
addListener: function(eventListener)
{
var eventListenerBar = new WebInspector.EventListenerBar(eventListener, this._nodeId, this._linkifier);
@@ -185,12 +193,17 @@
/**
* @constructor
* @extends {WebInspector.ObjectPropertiesSection}
+ * @param {!WebInspector.DOMModel.EventListener} eventListener
+ * @param {!DOMAgent.NodeId} nodeId
+ * @param {!WebInspector.Linkifier} linkifier
*/
WebInspector.EventListenerBar = function(eventListener, nodeId, linkifier)
{
- WebInspector.ObjectPropertiesSection.call(this, WebInspector.RemoteObject.fromPrimitiveValue(""));
+ var target = eventListener.target();
+ WebInspector.ObjectPropertiesSection.call(this, target.runtimeModel.createRemoteObjectFromPrimitiveValue(""));
- this.eventListener = eventListener;
+ this._runtimeModel = target.runtimeModel;
+ this._eventListener = eventListener;
this._nodeId = nodeId;
this._setNodeTitle();
this._setFunctionSubtitle(linkifier);
@@ -210,29 +223,30 @@
function updateWithNodeObject(nodeObject)
{
var properties = [];
+ var payload = this._eventListener.payload();
- properties.push(WebInspector.RemoteObjectProperty.fromPrimitiveValue("type", this.eventListener.type));
- properties.push(WebInspector.RemoteObjectProperty.fromPrimitiveValue("useCapture", this.eventListener.useCapture));
- properties.push(WebInspector.RemoteObjectProperty.fromPrimitiveValue("isAttribute", this.eventListener.isAttribute));
+ properties.push(this._runtimeModel.createRemotePropertyFromPrimitiveValue("type", payload.type));
+ properties.push(this._runtimeModel.createRemotePropertyFromPrimitiveValue("useCapture", payload.useCapture));
+ properties.push(this._runtimeModel.createRemotePropertyFromPrimitiveValue("isAttribute", payload.isAttribute));
if (nodeObject)
properties.push(new WebInspector.RemoteObjectProperty("node", nodeObject));
- if (typeof this.eventListener.handler !== "undefined") {
- var remoteObject = WebInspector.RemoteObject.fromPayload(this.eventListener.handler);
+ if (typeof payload.handler !== "undefined") {
+ var remoteObject = this._runtimeModel.createRemoteObject(payload.handler);
properties.push(new WebInspector.RemoteObjectProperty("handler", remoteObject));
}
- properties.push(WebInspector.RemoteObjectProperty.fromPrimitiveValue("listenerBody", this.eventListener.handlerBody));
- if (this.eventListener.sourceName)
- properties.push(WebInspector.RemoteObjectProperty.fromPrimitiveValue("sourceName", this.eventListener.sourceName));
- properties.push(WebInspector.RemoteObjectProperty.fromPrimitiveValue("lineNumber", this.eventListener.location.lineNumber + 1));
+ properties.push(this._runtimeModel.createRemotePropertyFromPrimitiveValue("listenerBody", payload.handlerBody));
+ if (payload.sourceName)
+ properties.push(this._runtimeModel.createRemotePropertyFromPrimitiveValue("sourceName", payload.sourceName));
+ properties.push(this._runtimeModel.createRemotePropertyFromPrimitiveValue("lineNumber", payload.location.lineNumber + 1));
this.updateProperties(properties);
}
- WebInspector.RemoteObject.resolveNode(this.eventListener.node, WebInspector.EventListenersSidebarPane._objectGroupName, updateWithNodeObject.bind(this));
+ this._eventListener.node().resolveToObject(WebInspector.EventListenersSidebarPane._objectGroupName, updateWithNodeObject.bind(this));
},
_setNodeTitle: function()
{
- var node = this.eventListener.node;
+ var node = this._eventListener.node();
if (!node)
return;
@@ -247,20 +261,13 @@
}
this.titleElement.removeChildren();
- this.titleElement.appendChild(WebInspector.DOMPresentationUtils.linkifyNodeReference(this.eventListener.node));
+ this.titleElement.appendChild(WebInspector.DOMPresentationUtils.linkifyNodeReference(node));
},
_setFunctionSubtitle: function(linkifier)
{
this.subtitleElement.removeChildren();
- var urlElement = linkifier.linkifyRawLocation(this.eventListener.location);
- if (!urlElement) {
- var url = this.eventListener.sourceName;
- var lineNumber = this.eventListener.location.lineNumber;
- var columnNumber = 0;
- urlElement = linkifier.linkifyLocation(url, lineNumber, columnNumber);
- }
- this.subtitleElement.appendChild(urlElement);
+ this.subtitleElement.appendChild(linkifier.linkifyRawLocation(this._eventListener.location()));
},
__proto__: WebInspector.ObjectPropertiesSection.prototype
diff --git a/Source/devtools/front_end/ExtensionAuditCategory.js b/Source/devtools/front_end/ExtensionAuditCategory.js
index a78567c..612a624 100644
--- a/Source/devtools/front_end/ExtensionAuditCategory.js
+++ b/Source/devtools/front_end/ExtensionAuditCategory.js
@@ -63,14 +63,15 @@
/**
* @override
+ * @param {!WebInspector.Target} target
* @param {!Array.<!WebInspector.NetworkRequest>} requests
- * @param {function(!WebInspector.AuditRuleResult)} ruleResultCallback
+ * @param {function(!WebInspector.AuditRuleResult)} ruleResultCallback
* @param {function()} categoryDoneCallback
* @param {!WebInspector.Progress} progress
*/
- run: function(requests, ruleResultCallback, categoryDoneCallback, progress)
+ run: function(target, requests, ruleResultCallback, categoryDoneCallback, progress)
{
- var results = new WebInspector.ExtensionAuditCategoryResults(this, ruleResultCallback, categoryDoneCallback, progress);
+ var results = new WebInspector.ExtensionAuditCategoryResults(this, target, ruleResultCallback, categoryDoneCallback, progress);
WebInspector.extensionServer.startAuditRun(this, results);
}
}
@@ -78,12 +79,14 @@
/**
* @constructor
* @param {!WebInspector.ExtensionAuditCategory} category
+ * @param {!WebInspector.Target} target
* @param {function(!WebInspector.AuditRuleResult)} ruleResultCallback
* @param {function()} categoryDoneCallback
* @param {!WebInspector.Progress} progress
*/
-WebInspector.ExtensionAuditCategoryResults = function(category, ruleResultCallback, categoryDoneCallback, progress)
+WebInspector.ExtensionAuditCategoryResults = function(category, target, ruleResultCallback, categoryDoneCallback, progress)
{
+ this._target = target;
this._category = category;
this._ruleResultCallback = ruleResultCallback;
this._categoryDoneCallback = categoryDoneCallback;
@@ -153,15 +156,16 @@
* @param {?string} error
* @param {!RuntimeAgent.RemoteObject} result
* @param {boolean=} wasThrown
+ * @this {WebInspector.ExtensionAuditCategoryResults}
*/
function onEvaluate(error, result, wasThrown)
{
if (wasThrown)
return;
- var object = WebInspector.RemoteObject.fromPayload(result);
+ var object = this._target.runtimeModel.createRemoteObject(result);
callback(object);
}
- WebInspector.extensionServer.evaluate(expression, false, false, evaluateOptions, this._category._extensionOrigin, onEvaluate);
+ WebInspector.extensionServer.evaluate(expression, false, false, evaluateOptions, this._category._extensionOrigin, onEvaluate.bind(this));
}
}
diff --git a/Source/devtools/front_end/ExtensionPanel.js b/Source/devtools/front_end/ExtensionPanel.js
index 71eca98..abb3fa4 100644
--- a/Source/devtools/front_end/ExtensionPanel.js
+++ b/Source/devtools/front_end/ExtensionPanel.js
@@ -217,7 +217,7 @@
if (error)
callback(error.toString());
else
- this._setObject(WebInspector.RemoteObject.fromPayload(result), title, callback);
+ this._setObject(WebInspector.runtimeModel.createRemoteObject(result), title, callback);
},
_createObjectPropertiesView: function()
diff --git a/Source/devtools/front_end/ExtensionServer.js b/Source/devtools/front_end/ExtensionServer.js
index 7f3601b..4c0f7a5 100644
--- a/Source/devtools/front_end/ExtensionServer.js
+++ b/Source/devtools/front_end/ExtensionServer.js
@@ -417,6 +417,7 @@
return this._status.E_BADARG("message.severity", message.severity);
var consoleMessage = new WebInspector.ConsoleMessage(
+ WebInspector.console.target(),
WebInspector.ConsoleMessage.MessageSource.JS,
level,
message.text,
diff --git a/Source/devtools/front_end/FilteredItemSelectionDialog.js b/Source/devtools/front_end/FilteredItemSelectionDialog.js
index e530eeb..6543075 100644
--- a/Source/devtools/front_end/FilteredItemSelectionDialog.js
+++ b/Source/devtools/front_end/FilteredItemSelectionDialog.js
@@ -72,19 +72,20 @@
*/
position: function(element, relativeToElement)
{
- const minWidth = 500;
- const minHeight = 204;
- var width = Math.max(relativeToElement.offsetWidth * 2 / 3, minWidth);
- var height = Math.max(relativeToElement.offsetHeight * 2 / 3, minHeight);
+ const shadow = 10;
+ const shadowPadding = 20; // shadow + padding
+ var container = WebInspector.Dialog.modalHostView().element;
+ var preferredWidth = Math.max(relativeToElement.offsetWidth * 2 / 3, 500);
+ var width = Math.min(preferredWidth, container.offsetWidth - 2 * shadowPadding);
+ var preferredHeight = Math.max(relativeToElement.offsetHeight * 2 / 3, 204);
+ var height = Math.min(preferredHeight, container.offsetHeight - 2 * shadowPadding);
this.element.style.width = width + "px";
- var container = WebInspector.Dialog.modalHostView().element;
var box = relativeToElement.boxInWindow(window).relativeToElement(container);
- const shadowPadding = 20; // shadow + padding
- var positionX = box.x + Math.max((box.width - width - 2 * shadowPadding) / 2, shadowPadding);
- positionX = Math.max(shadowPadding, Math.min(container.offsetWidth - width - 2 * shadowPadding, positionX));
- var positionY = box.y + Math.max((box.height - height - 2 * shadowPadding) / 2, shadowPadding);
- positionY = Math.max(shadowPadding, Math.min(container.offsetHeight - height - 2 * shadowPadding, positionY));
+ var positionX = box.x + Math.max((box.width - width - 2 * shadowPadding) / 2, shadow);
+ positionX = Math.max(shadow, Math.min(container.offsetWidth - width - 2 * shadowPadding, positionX));
+ var positionY = box.y + Math.max((box.height - height - 2 * shadowPadding) / 2, shadow);
+ positionY = Math.max(shadow, Math.min(container.offsetHeight - height - 2 * shadowPadding, positionY));
element.positionAt(positionX, positionY, container);
this._dialogHeight = height;
diff --git a/Source/devtools/front_end/HandlerRegistry.js b/Source/devtools/front_end/HandlerRegistry.js
index d8c4fff..e1ad731 100644
--- a/Source/devtools/front_end/HandlerRegistry.js
+++ b/Source/devtools/front_end/HandlerRegistry.js
@@ -278,6 +278,32 @@
}
/**
+ * @constructor
+ * @extends {WebInspector.UISettingDelegate}
+ */
+WebInspector.HandlerRegistry.OpenAnchorLocationSettingDelegate = function()
+{
+ WebInspector.UISettingDelegate.call(this);
+}
+
+WebInspector.HandlerRegistry.OpenAnchorLocationSettingDelegate.prototype = {
+ /**
+ * @override
+ * @return {?Element}
+ */
+ settingElement: function()
+ {
+ if (!WebInspector.openAnchorLocationRegistry.handlerNames.length)
+ return null;
+
+ var handlerSelector = new WebInspector.HandlerSelector(WebInspector.openAnchorLocationRegistry);
+ return WebInspector.SettingsUI.createCustomSetting(WebInspector.UIString("Open links in"), handlerSelector.element);
+ },
+
+ __proto__: WebInspector.UISettingDelegate.prototype
+}
+
+/**
* @type {!WebInspector.HandlerRegistry}
*/
WebInspector.openAnchorLocationRegistry;
diff --git a/Source/devtools/front_end/HeapSnapshot.js b/Source/devtools/front_end/HeapSnapshot.js
index cd14851..f37c747 100644
--- a/Source/devtools/front_end/HeapSnapshot.js
+++ b/Source/devtools/front_end/HeapSnapshot.js
@@ -1512,51 +1512,51 @@
var edgeShortcutType = this._edgeShortcutType;
var firstEdgeIndexes = this._firstEdgeIndexes;
var containmentEdges = this._containmentEdges;
- var containmentEdgesLength = this._containmentEdges.length;
var mapAndFlag = this.userObjectsMapAndFlag();
var flags = mapAndFlag ? mapAndFlag.map : null;
var flag = mapAndFlag ? mapAndFlag.flag : 0;
- var nodesToVisit = new Uint32Array(nodeCount);
+ var stackNodes = new Uint32Array(nodeCount);
+ var stackCurrentEdge = new Uint32Array(nodeCount);
var postOrderIndex2NodeOrdinal = new Uint32Array(nodeCount);
var nodeOrdinal2PostOrderIndex = new Uint32Array(nodeCount);
- var painted = new Uint8Array(nodeCount);
- var nodesToVisitLength = 0;
+ var visited = new Uint8Array(nodeCount);
var postOrderIndex = 0;
- var grey = 1;
- var black = 2;
- nodesToVisit[nodesToVisitLength++] = rootNodeOrdinal;
- painted[rootNodeOrdinal] = grey;
+ var stackTop = 0;
+ stackNodes[0] = rootNodeOrdinal;
+ stackCurrentEdge[0] = firstEdgeIndexes[rootNodeOrdinal];
+ visited[rootNodeOrdinal] = 1;
- while (nodesToVisitLength) {
- var nodeOrdinal = nodesToVisit[nodesToVisitLength - 1];
+ while (stackTop >= 0) {
+ var nodeOrdinal = stackNodes[stackTop];
+ var edgeIndex = stackCurrentEdge[stackTop];
+ var edgesEnd = firstEdgeIndexes[nodeOrdinal + 1];
- if (painted[nodeOrdinal] === grey) {
- painted[nodeOrdinal] = black;
+ if (edgeIndex < edgesEnd) {
+ stackCurrentEdge[stackTop] += edgeFieldsCount;
+ if (nodeOrdinal !== rootNodeOrdinal && containmentEdges[edgeIndex + edgeTypeOffset] === edgeShortcutType)
+ continue;
+ var childNodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset];
+ var childNodeOrdinal = childNodeIndex / nodeFieldCount;
+ if (visited[childNodeOrdinal])
+ continue;
var nodeFlag = !flags || (flags[nodeOrdinal] & flag);
- var beginEdgeIndex = firstEdgeIndexes[nodeOrdinal];
- var endEdgeIndex = firstEdgeIndexes[nodeOrdinal + 1];
- for (var edgeIndex = beginEdgeIndex; edgeIndex < endEdgeIndex; edgeIndex += edgeFieldsCount) {
- if (nodeOrdinal !== rootNodeOrdinal && containmentEdges[edgeIndex + edgeTypeOffset] === edgeShortcutType)
- continue;
- var childNodeIndex = containmentEdges[edgeIndex + edgeToNodeOffset];
- var childNodeOrdinal = childNodeIndex / nodeFieldCount;
- var childNodeFlag = !flags || (flags[childNodeOrdinal] & flag);
- // We are skipping the edges from non-page-owned nodes to page-owned nodes.
- // Otherwise the dominators for the objects that also were retained by debugger would be affected.
- if (nodeOrdinal !== rootNodeOrdinal && childNodeFlag && !nodeFlag)
- continue;
- if (!painted[childNodeOrdinal]) {
- painted[childNodeOrdinal] = grey;
- nodesToVisit[nodesToVisitLength++] = childNodeOrdinal;
- }
- }
+ var childNodeFlag = !flags || (flags[childNodeOrdinal] & flag);
+ // We are skipping the edges from non-page-owned nodes to page-owned nodes.
+ // Otherwise the dominators for the objects that also were retained by debugger would be affected.
+ if (nodeOrdinal !== rootNodeOrdinal && childNodeFlag && !nodeFlag)
+ continue;
+ ++stackTop;
+ stackNodes[stackTop] = childNodeOrdinal;
+ stackCurrentEdge[stackTop] = firstEdgeIndexes[childNodeOrdinal];
+ visited[childNodeOrdinal] = 1;
} else {
+ // Done with all the node children
nodeOrdinal2PostOrderIndex[nodeOrdinal] = postOrderIndex;
postOrderIndex2NodeOrdinal[postOrderIndex++] = nodeOrdinal;
- --nodesToVisitLength;
+ --stackTop;
}
}
@@ -1564,7 +1564,7 @@
console.log("Error: Corrupted snapshot. " + (nodeCount - postOrderIndex) + " nodes are unreachable from the root:");
var dumpNode = this.rootNode();
for (var i = 0; i < nodeCount; ++i) {
- if (painted[i] !== black) {
+ if (!visited[i]) {
// Fix it by giving the node a postorder index anyway.
nodeOrdinal2PostOrderIndex[i] = postOrderIndex;
postOrderIndex2NodeOrdinal[postOrderIndex++] = i;
diff --git a/Source/devtools/front_end/HeapSnapshotDataGrids.js b/Source/devtools/front_end/HeapSnapshotDataGrids.js
index 3789abb..d6044ff 100644
--- a/Source/devtools/front_end/HeapSnapshotDataGrids.js
+++ b/Source/devtools/front_end/HeapSnapshotDataGrids.js
@@ -580,6 +580,7 @@
{
this.element = document.createElement("tr");
this.element.classList.add("revealed");
+ this.setHeight(0);
}
WebInspector.HeapSnapshotPaddingNode.prototype = {
@@ -1054,7 +1055,7 @@
/**
* @constructor
- * @extends {WebInspector.DataGrid}
+ * @extends {WebInspector.HeapSnapshotViewportDataGrid}
*/
WebInspector.AllocationDataGrid = function()
{
@@ -1065,9 +1066,8 @@
{id: "size", title: WebInspector.UIString("Size"), width: "72px", sortable: true, sort: WebInspector.DataGrid.Order.Descending},
{id: "name", title: WebInspector.UIString("Function"), disclosure: true, sortable: true},
];
- WebInspector.DataGrid.call(this, columns);
+ WebInspector.HeapSnapshotViewportDataGrid.call(this, columns);
this._linkifier = new WebInspector.Linkifier();
- this.addEventListener(WebInspector.DataGrid.Events.SortingChanged, this._sortingChanged, this);
}
WebInspector.AllocationDataGrid.prototype = {
@@ -1089,13 +1089,15 @@
_populateChildren: function()
{
+ this.removeTopLevelNodes();
var root = this.rootNode();
var tops = this._topNodes;
for (var i = 0; i < tops.length; i++)
- root.appendChild(new WebInspector.AllocationGridNode(this, tops[i]));
+ this.appendNode(root, new WebInspector.AllocationGridNode(this, tops[i]));
+ this.updateVisibleNodes(true);
},
- _sortingChanged: function()
+ sortingChanged: function()
{
this._topNodes.sort(this._createComparator());
this.rootNode().removeChildren();
@@ -1126,5 +1128,5 @@
return compare;
},
- __proto__: WebInspector.DataGrid.prototype
+ __proto__: WebInspector.HeapSnapshotViewportDataGrid.prototype
}
diff --git a/Source/devtools/front_end/HeapSnapshotGridNodes.js b/Source/devtools/front_end/HeapSnapshotGridNodes.js
index 2bf3932..a2e4033 100644
--- a/Source/devtools/front_end/HeapSnapshotGridNodes.js
+++ b/Source/devtools/front_end/HeapSnapshotGridNodes.js
@@ -230,7 +230,7 @@
_createValueCell: function(columnIdentifier)
{
var cell = document.createElement("td");
- cell.className = columnIdentifier + "-column";
+ cell.className = "numeric-column";
if (this.dataGrid.snapshot.totalSize !== 0) {
var div = document.createElement("div");
var valueSpan = document.createElement("span");
@@ -619,13 +619,13 @@
function formatResult(error, object)
{
if (!error && object.type)
- callback(WebInspector.RemoteObject.fromPayload(object), !!error);
+ callback(WebInspector.runtimeModel.createRemoteObject(object), !!error);
else
- callback(WebInspector.RemoteObject.fromPrimitiveValue(WebInspector.UIString("Preview is not available")));
+ callback(WebInspector.runtimeModel.createRemoteObjectFromPrimitiveValue(WebInspector.UIString("Preview is not available")));
}
if (this._type === "string")
- callback(WebInspector.RemoteObject.fromPrimitiveValue(this._name));
+ callback(WebInspector.runtimeModel.createRemoteObjectFromPrimitiveValue(this._name));
else
HeapProfilerAgent.getObjectByHeapObjectId(String(this.snapshotNodeId), objectGroupName, formatResult);
},
@@ -1185,6 +1185,18 @@
this._removedCount);
},
+ /**
+ * @param {string} columnIdentifier
+ * @return {!Element}
+ */
+ createCell: function(columnIdentifier)
+ {
+ var cell = WebInspector.HeapSnapshotGridNode.prototype.createCell.call(this, columnIdentifier);
+ if (columnIdentifier !== "object")
+ cell.classList.add("numeric-column");
+ return cell;
+ },
+
_createChildNode: function(item)
{
if (item.isAddedNotRemoved)
@@ -1342,15 +1354,22 @@
/**
* @constructor
- * @extends {WebInspector.DataGridNode}
+ * @extends {WebInspector.HeapSnapshotGridNode}
* @param {!WebInspector.AllocationDataGrid} dataGrid
* @param {!WebInspector.HeapSnapshotCommon.SerializedAllocationNode} data
*/
WebInspector.AllocationGridNode = function(dataGrid, data)
{
- WebInspector.DataGridNode.call(this, data, data.hasChildren);
- this._dataGrid = dataGrid;
+ WebInspector.HeapSnapshotGridNode.call(this, dataGrid, data.hasChildren);
this._populated = false;
+ this._allocationNode = data;
+ this.data = {
+ "liveCount": Number.withThousandsSeparator(data.liveCount),
+ "count": Number.withThousandsSeparator(data.count),
+ "liveSize": Number.withThousandsSeparator(data.liveSize),
+ "size": Number.withThousandsSeparator(data.size),
+ "name": data.name
+ };
}
WebInspector.AllocationGridNode.prototype = {
@@ -1359,7 +1378,7 @@
if (this._populated)
return;
this._populated = true;
- this._dataGrid.snapshot.allocationNodeCallers(this.data.id, didReceiveCallers.bind(this));
+ this._dataGrid.snapshot.allocationNodeCallers(this._allocationNode.id, didReceiveCallers.bind(this));
/**
* @param {!WebInspector.HeapSnapshotCommon.AllocationNodeCallers} callers
@@ -1369,9 +1388,10 @@
{
var callersChain = callers.nodesWithSingleCaller;
var parentNode = this;
+ var dataGrid = /** @type {!WebInspector.AllocationDataGrid} */ (this._dataGrid);
for (var i = 0; i < callersChain.length; i++) {
- var child = new WebInspector.AllocationGridNode(this._dataGrid, callersChain[i]);
- parentNode.appendChild(child);
+ var child = new WebInspector.AllocationGridNode(dataGrid, callersChain[i]);
+ dataGrid.appendNode(parentNode, child);
parentNode = child;
parentNode._populated = true;
if (this.expanded)
@@ -1381,7 +1401,8 @@
var callersBranch = callers.branchingCallers;
callersBranch.sort(this._dataGrid._createComparator());
for (var i = 0; i < callersBranch.length; i++)
- parentNode.appendChild(new WebInspector.AllocationGridNode(this._dataGrid, callersBranch[i]));
+ dataGrid.appendNode(parentNode, new WebInspector.AllocationGridNode(dataGrid, callersBranch[i]));
+ dataGrid.updateVisibleNodes(true);
}
},
@@ -1390,7 +1411,7 @@
*/
expand: function()
{
- WebInspector.DataGridNode.prototype.expand.call(this);
+ WebInspector.HeapSnapshotGridNode.prototype.expand.call(this);
if (this.children.length === 1)
this.children[0].expand();
},
@@ -1402,14 +1423,13 @@
*/
createCell: function(columnIdentifier)
{
- var cell = WebInspector.DataGridNode.prototype.createCell.call(this, columnIdentifier);
-
if (columnIdentifier !== "name")
- return cell;
+ return this._createValueCell(columnIdentifier);
- var functionInfo = this.data;
- if (functionInfo.scriptName) {
- var urlElement = this._dataGrid._linkifier.linkifyLocation(functionInfo.scriptName, functionInfo.line - 1, functionInfo.column - 1, "profile-node-file");
+ var cell = WebInspector.HeapSnapshotGridNode.prototype.createCell.call(this, columnIdentifier);
+ var allocationNode = this._allocationNode;
+ if (allocationNode.scriptName) {
+ var urlElement = this._dataGrid._linkifier.linkifyLocation(allocationNode.scriptName, allocationNode.line - 1, allocationNode.column - 1, "profile-node-file");
urlElement.style.maxWidth = "75%";
cell.insertBefore(urlElement, cell.firstChild);
}
@@ -1425,5 +1445,5 @@
return this.data.id;
},
- __proto__: WebInspector.DataGridNode.prototype
+ __proto__: WebInspector.HeapSnapshotGridNode.prototype
}
diff --git a/Source/devtools/front_end/HeapSnapshotView.js b/Source/devtools/front_end/HeapSnapshotView.js
index 0ec075c..e094865 100644
--- a/Source/devtools/front_end/HeapSnapshotView.js
+++ b/Source/devtools/front_end/HeapSnapshotView.js
@@ -86,7 +86,7 @@
this._allocationDataGrid.show(this._allocationView.element);
}
- this._retainmentViewHeader = document.createElementWithClass("div", "retainers-view-header");
+ this._retainmentViewHeader = document.createElementWithClass("div", "heap-snapshot-view-resizer");
var retainingPathsTitleDiv = this._retainmentViewHeader.createChild("div", "title");
var retainingPathsTitle = retainingPathsTitleDiv.createChild("span");
retainingPathsTitle.textContent = WebInspector.UIString("Object's retaining tree");
@@ -375,6 +375,14 @@
{
WebInspector.HeapSnapshotView.Perspective.call(this, WebInspector.UIString("Allocation"));
this._allocationSplitView = new WebInspector.SplitView(false, true, "heapSnapshotAllocationSplitViewState", 200, 200);
+
+ var resizer = document.createElementWithClass("div", "heap-snapshot-view-resizer");
+ var title = resizer.createChild("div", "title").createChild("span");
+ title.textContent = WebInspector.UIString("Live objects");
+ this._allocationSplitView.hideDefaultResizer();
+ this._allocationSplitView.installResizer(resizer);
+
+ this._allocationSplitView.sidebarElement().appendChild(resizer);
}
WebInspector.HeapSnapshotView.AllocationPerspective.prototype = {
@@ -771,6 +779,7 @@
{
var selectedNode = event.target.selectedNode;
this._constructorsDataGrid.setAllocationNodeId(selectedNode.allocationNodeId());
+ this._setRetainmentDataGridSource(null);
},
_inspectedObjectChanged: function(event)
diff --git a/Source/devtools/front_end/InspectorView.js b/Source/devtools/front_end/InspectorView.js
index b87c2c5..f9abed1 100644
--- a/Source/devtools/front_end/InspectorView.js
+++ b/Source/devtools/front_end/InspectorView.js
@@ -293,8 +293,6 @@
{
if (!WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event))
return;
- if (WebInspector.Dialog.currentInstance())
- return;
var keyboardEvent = /** @type {!KeyboardEvent} */ (event);
// Ctrl/Cmd + 1-9 should show corresponding panel.
@@ -308,7 +306,8 @@
if (panelIndex !== -1) {
var panelName = this._tabbedPane.allTabs()[panelIndex];
if (panelName) {
- this.showPanel(panelName);
+ if (!WebInspector.Dialog.currentInstance())
+ this.showPanel(panelName);
event.consume(true);
}
return;
@@ -340,7 +339,8 @@
return;
if (!event.shiftKey && !event.altKey) {
- this._changePanelInDirection(direction);
+ if (!WebInspector.Dialog.currentInstance())
+ this._changePanelInDirection(direction);
event.consume(true);
return;
}
@@ -365,7 +365,8 @@
this._inHistory = true;
this._historyIterator = newIndex;
- this.setCurrentPanel(WebInspector.panels[this._history[this._historyIterator]]);
+ if (!WebInspector.Dialog.currentInstance())
+ this.setCurrentPanel(WebInspector.panels[this._history[this._historyIterator]]);
delete this._inHistory;
return true;
@@ -495,7 +496,7 @@
doResize: function()
{
- var size = this.minimumSize();
+ var size = this.constraints().minimum;
var right = Math.min(0, window.innerWidth - size.width);
this.element.style.right = right + "px";
var bottom = Math.min(0, window.innerHeight - size.height);
diff --git a/Source/devtools/front_end/JavaScriptSourceFrame.js b/Source/devtools/front_end/JavaScriptSourceFrame.js
index 52a6f7b..2af3ab7 100644
--- a/Source/devtools/front_end/JavaScriptSourceFrame.js
+++ b/Source/devtools/front_end/JavaScriptSourceFrame.js
@@ -319,26 +319,6 @@
_resolveObjectForPopover: function(anchorBox, showCallback, objectGroupName)
{
- /**
- * @param {?RuntimeAgent.RemoteObject} result
- * @param {boolean=} wasThrown
- * @this {WebInspector.JavaScriptSourceFrame}
- */
- function showObjectPopover(result, wasThrown)
- {
- if (!WebInspector.debuggerModel.isPaused() || !result) {
- this._popoverHelper.hidePopover();
- return;
- }
- this._popoverAnchorBox = anchorBox;
- showCallback(WebInspector.RemoteObject.fromPayload(result), wasThrown, this._popoverAnchorBox);
- // Popover may have been removed by showCallback().
- if (this._popoverAnchorBox) {
- var highlightRange = new WebInspector.TextRange(lineNumber, startHighlight, lineNumber, endHighlight);
- this._popoverAnchorBox._highlightDescriptor = this.textEditor.highlightRange(highlightRange, "source-frame-eval-expression");
- }
- }
-
if (!WebInspector.debuggerModel.isPaused()) {
this._popoverHelper.hidePopover();
return;
@@ -360,6 +340,26 @@
var evaluationText = line.substring(startHighlight, endHighlight + 1);
var selectedCallFrame = WebInspector.debuggerModel.selectedCallFrame();
selectedCallFrame.evaluate(evaluationText, objectGroupName, false, true, false, false, showObjectPopover.bind(this));
+
+ /**
+ * @param {?RuntimeAgent.RemoteObject} result
+ * @param {boolean=} wasThrown
+ * @this {WebInspector.JavaScriptSourceFrame}
+ */
+ function showObjectPopover(result, wasThrown)
+ {
+ if (!WebInspector.debuggerModel.isPaused() || !result) {
+ this._popoverHelper.hidePopover();
+ return;
+ }
+ this._popoverAnchorBox = anchorBox;
+ showCallback(selectedCallFrame.target().runtimeModel.createRemoteObject(result), wasThrown, this._popoverAnchorBox);
+ // Popover may have been removed by showCallback().
+ if (this._popoverAnchorBox) {
+ var highlightRange = new WebInspector.TextRange(lineNumber, startHighlight, lineNumber, endHighlight);
+ this._popoverAnchorBox._highlightDescriptor = this.textEditor.highlightRange(highlightRange, "source-frame-eval-expression");
+ }
+ }
},
_onHidePopover: function()
@@ -651,7 +651,7 @@
_continueToLine: function(lineNumber)
{
var rawLocation = /** @type {!WebInspector.DebuggerModel.Location} */ (this._uiSourceCode.uiLocationToRawLocation(lineNumber, 0));
- this._scriptsPanel.continueToLocation(rawLocation);
+ rawLocation.continueToLocation();
},
dispose: function()
diff --git a/Source/devtools/front_end/Linkifier.js b/Source/devtools/front_end/Linkifier.js
index 2840b61..8c00c1e 100644
--- a/Source/devtools/front_end/Linkifier.js
+++ b/Source/devtools/front_end/Linkifier.js
@@ -131,11 +131,12 @@
*/
linkifyRawLocation: function(rawLocation, classes)
{
- var script = WebInspector.debuggerModel.scriptForId(rawLocation.scriptId);
+ // FIXME: this check should not be here.
+ var script = rawLocation.target().debuggerModel.scriptForId(rawLocation.scriptId);
if (!script)
return null;
var anchor = this._createAnchor(classes);
- var liveLocation = script.createLiveLocation(rawLocation, this._updateAnchor.bind(this, anchor));
+ var liveLocation = rawLocation.createLiveLocation(this._updateAnchor.bind(this, anchor));
this._liveLocations.push(liveLocation);
return anchor;
},
@@ -149,7 +150,7 @@
linkifyCSSLocation: function(styleSheetId, rawLocation, classes)
{
var anchor = this._createAnchor(classes);
- var liveLocation = WebInspector.cssModel.createLiveLocation(styleSheetId, rawLocation, this._updateAnchor.bind(this, anchor));
+ var liveLocation = rawLocation.createLiveLocation(styleSheetId, this._updateAnchor.bind(this, anchor));
if (!liveLocation)
return null;
this._liveLocations.push(liveLocation);
diff --git a/Source/devtools/front_end/LiveEditSupport.js b/Source/devtools/front_end/LiveEditSupport.js
index ed1d1e6..2a7ffab 100644
--- a/Source/devtools/front_end/LiveEditSupport.js
+++ b/Source/devtools/front_end/LiveEditSupport.js
@@ -48,7 +48,7 @@
{
var rawLocation = uiSourceCode.uiLocationToRawLocation(0, 0);
var debuggerModelLocation = /** @type {!WebInspector.DebuggerModel.Location} */ (rawLocation);
- var script = WebInspector.debuggerModel.scriptForId(debuggerModelLocation.scriptId);
+ var script = debuggerModelLocation.script();
var uiLocation = script.rawLocationToUILocation(0, 0);
// FIXME: Support live editing of scripts mapped to some file.
diff --git a/Source/devtools/front_end/Main.js b/Source/devtools/front_end/Main.js
index 3532dee..e82b996 100644
--- a/Source/devtools/front_end/Main.js
+++ b/Source/devtools/front_end/Main.js
@@ -273,7 +273,6 @@
WebInspector.isolatedFileSystemDispatcher = new WebInspector.IsolatedFileSystemDispatcher(WebInspector.isolatedFileSystemManager);
WebInspector.workspace = new WebInspector.Workspace(WebInspector.isolatedFileSystemManager.mapping());
- WebInspector.cssModel = new WebInspector.CSSStyleModel(WebInspector.workspace);
WebInspector.timelineManager = new WebInspector.TimelineManager();
WebInspector.tracingAgent = new WebInspector.TracingAgent();
@@ -368,7 +367,7 @@
WebInspector.databaseModel = new WebInspector.DatabaseModel();
WebInspector.domStorageModel = new WebInspector.DOMStorageModel();
- WebInspector.cpuProfilerModel = new WebInspector.CPUProfilerModel();
+ WebInspector.cpuProfilerModel = new WebInspector.CPUProfilerModel(mainTarget);
InspectorAgent.enable(inspectorAgentEnableCallback.bind(this));
@@ -520,10 +519,8 @@
{
if (event.handled)
return;
- if (WebInspector.Dialog.currentInstance())
- return;
- if (WebInspector.inspectorView.currentPanel()) {
+ if (!WebInspector.Dialog.currentInstance() && WebInspector.inspectorView.currentPanel()) {
WebInspector.inspectorView.currentPanel().handleShortcut(event);
if (event.handled) {
event.consume(true);
@@ -531,15 +528,15 @@
}
}
- if (WebInspector.advancedSearchController.handleShortcut(event))
+ if (!WebInspector.Dialog.currentInstance() && WebInspector.advancedSearchController.handleShortcut(event))
return;
- if (WebInspector.inspectElementModeController && WebInspector.inspectElementModeController.handleShortcut(event))
+ if (!WebInspector.Dialog.currentInstance() && WebInspector.inspectElementModeController && WebInspector.inspectElementModeController.handleShortcut(event))
return;
var isValidZoomShortcut = WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event) &&
!event.altKey &&
!InspectorFrontendHost.isStub;
- if (isValidZoomShortcut && this._handleZoomEvent(event)) {
+ if (!WebInspector.Dialog.currentInstance() && isValidZoomShortcut && this._handleZoomEvent(event)) {
event.consume(true);
return;
}
@@ -592,7 +589,7 @@
*/
inspect: function(payload, hints)
{
- var object = WebInspector.RemoteObject.fromPayload(payload);
+ var object = WebInspector.runtimeModel.createRemoteObject(payload);
if (object.subtype === "node") {
object.pushNodeToFrontend(callback);
var elementsPanel = /** @type {!WebInspector.ElementsPanel} */ (WebInspector.inspectorView.panel("elements"));
@@ -616,25 +613,21 @@
* @param {?Protocol.Error} error
* @param {!DebuggerAgent.FunctionDetails} response
*/
- DebuggerAgent.getFunctionDetails(object.objectId, didGetDetails);
+ object.functionDetails(didGetDetails);
return;
}
- function didGetDetails(error, response)
+ /**
+ * @param {?DebuggerAgent.FunctionDetails} response
+ */
+ function didGetDetails(response)
{
object.release();
- if (error) {
- console.error(error);
- return;
- }
-
- var uiLocation = WebInspector.debuggerModel.rawLocationToUILocation(response.location);
- if (!uiLocation)
+ if (!response)
return;
- // FIXME: Dependency violation.
- /** @type {!WebInspector.SourcesPanel} */ (WebInspector.inspectorView.panel("sources")).showUILocation(uiLocation, true);
+ WebInspector.Revealer.reveal(WebInspector.DebuggerModel.Location.fromPayload(object.target(), response.location).toUILocation());
}
if (hints.copyToClipboard)
@@ -693,8 +686,10 @@
*/
handleAction: function()
{
- WebInspector.debuggerModel.skipAllPauses(true, true);
- WebInspector.resourceTreeModel.reloadPage(false);
+ if (!WebInspector.Dialog.currentInstance()) {
+ WebInspector.debuggerModel.skipAllPauses(true, true);
+ WebInspector.resourceTreeModel.reloadPage(false);
+ }
return true;
}
}
@@ -713,8 +708,10 @@
*/
handleAction: function()
{
- WebInspector.debuggerModel.skipAllPauses(true, true);
- WebInspector.resourceTreeModel.reloadPage(true);
+ if (!WebInspector.Dialog.currentInstance()) {
+ WebInspector.debuggerModel.skipAllPauses(true, true);
+ WebInspector.resourceTreeModel.reloadPage(true);
+ }
return true;
}
}
@@ -738,6 +735,29 @@
}
}
+/**
+ * @constructor
+ * @extends {WebInspector.UISettingDelegate}
+ */
+WebInspector.Main.ShortcutPanelSwitchSettingDelegate = function()
+{
+ WebInspector.UISettingDelegate.call(this);
+}
+
+WebInspector.Main.ShortcutPanelSwitchSettingDelegate.prototype = {
+ /**
+ * @override
+ * @return {!Element}
+ */
+ settingElement: function()
+ {
+ var modifier = WebInspector.platform() === "mac" ? "Cmd" : "Ctrl";
+ return WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("Enable %s + 1-9 shortcut to switch panels", modifier), WebInspector.settings.shortcutPanelSwitch);
+ },
+
+ __proto__: WebInspector.UISettingDelegate.prototype
+}
+
new WebInspector.Main();
window.DEBUG = true;
diff --git a/Source/devtools/front_end/MemoryCountersGraph.js b/Source/devtools/front_end/MemoryCountersGraph.js
index 0b0237c..fe3047d 100644
--- a/Source/devtools/front_end/MemoryCountersGraph.js
+++ b/Source/devtools/front_end/MemoryCountersGraph.js
@@ -47,6 +47,14 @@
}
WebInspector.MemoryCountersGraph.prototype = {
+ timelineStarted: function()
+ {
+ },
+
+ timelineStopped: function()
+ {
+ },
+
/**
* @param {!WebInspector.TimelineModel.Record} record
*/
diff --git a/Source/devtools/front_end/MetricsSidebarPane.js b/Source/devtools/front_end/MetricsSidebarPane.js
index f39f6c5..85f3b73 100644
--- a/Source/devtools/front_end/MetricsSidebarPane.js
+++ b/Source/devtools/front_end/MetricsSidebarPane.js
@@ -33,12 +33,6 @@
WebInspector.MetricsSidebarPane = function()
{
WebInspector.SidebarPane.call(this, WebInspector.UIString("Metrics"));
-
- WebInspector.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetChanged, this._styleSheetOrMediaQueryResultChanged, this);
- WebInspector.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.MediaQueryResultChanged, this._styleSheetOrMediaQueryResultChanged, this);
- WebInspector.domModel.addEventListener(WebInspector.DOMModel.Events.AttrModified, this._attributesUpdated, this);
- WebInspector.domModel.addEventListener(WebInspector.DOMModel.Events.AttrRemoved, this._attributesUpdated, this);
- WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameResized, this._frameResized, this);
}
WebInspector.MetricsSidebarPane.prototype = {
@@ -47,11 +41,39 @@
*/
update: function(node)
{
- if (node)
- this.node = node;
+ if (!node || this._node === node) {
+ this._innerUpdate();
+ return;
+ }
+
+ this._node = node;
+ this._updateTarget(node.target());
this._innerUpdate();
},
+ /**
+ * @param {!WebInspector.Target} target
+ */
+ _updateTarget: function(target)
+ {
+ if (this._target === target)
+ return;
+
+ if (this._target) {
+ this._target.cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.StyleSheetChanged, this._styleSheetOrMediaQueryResultChanged, this);
+ this._target.cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.MediaQueryResultChanged, this._styleSheetOrMediaQueryResultChanged, this);
+ this._target.domModel.removeEventListener(WebInspector.DOMModel.Events.AttrModified, this._attributesUpdated, this);
+ this._target.domModel.removeEventListener(WebInspector.DOMModel.Events.AttrRemoved, this._attributesUpdated, this);
+ this._target.resourceTreeModel.removeEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameResized, this._frameResized, this);
+ }
+ this._target = target;
+ this._target.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetChanged, this._styleSheetOrMediaQueryResultChanged, this);
+ this._target.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.MediaQueryResultChanged, this._styleSheetOrMediaQueryResultChanged, this);
+ this._target.domModel.addEventListener(WebInspector.DOMModel.Events.AttrModified, this._attributesUpdated, this);
+ this._target.domModel.addEventListener(WebInspector.DOMModel.Events.AttrRemoved, this._attributesUpdated, this);
+ this._target.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameResized, this._frameResized, this);
+ },
+
_innerUpdate: function()
{
// "style" attribute might have changed. Update metrics unless they are being edited
@@ -60,7 +82,7 @@
return;
// FIXME: avoid updates of a collapsed pane.
- var node = this.node;
+ var node = this._node;
if (!node || node.nodeType() !== Node.ELEMENT_NODE) {
this.bodyElement.removeChildren();
@@ -73,11 +95,11 @@
*/
function callback(style)
{
- if (!style || this.node !== node)
+ if (!style || this._node !== node)
return;
this._updateMetrics(style);
}
- WebInspector.cssModel.getComputedStyleAsync(node.id, callback.bind(this));
+ this._target.cssModel.getComputedStyleAsync(node.id, callback.bind(this));
/**
* @param {?WebInspector.CSSStyleDeclaration} style
@@ -85,11 +107,11 @@
*/
function inlineStyleCallback(style)
{
- if (!style || this.node !== node)
+ if (!style || this._node !== node)
return;
this.inlineStyle = style;
}
- WebInspector.cssModel.getInlineStylesAsync(node.id, inlineStyleCallback.bind(this));
+ this._target.cssModel.getInlineStylesAsync(node.id, inlineStyleCallback.bind(this));
},
_styleSheetOrMediaQueryResultChanged: function()
@@ -116,7 +138,7 @@
_attributesUpdated: function(event)
{
- if (this.node !== event.data.node)
+ if (this._node !== event.data.node)
return;
this._innerUpdate();
@@ -140,20 +162,19 @@
_highlightDOMNode: function(showHighlight, mode, event)
{
event.consume();
- var nodeId = showHighlight && this.node ? this.node.id : 0;
- if (nodeId) {
+ if (showHighlight && this._node) {
if (this._highlightMode === mode)
return;
this._highlightMode = mode;
- WebInspector.domModel.highlightDOMNode(nodeId, mode);
+ this._node.highlight(mode);
} else {
delete this._highlightMode;
- WebInspector.domModel.hideDOMNodeHighlight();
+ this._target.domModel.hideDOMNodeHighlight();
}
for (var i = 0; this._boxElements && i < this._boxElements.length; ++i) {
var element = this._boxElements[i];
- if (!nodeId || mode === "all" || element._name === mode)
+ if (!this._node || mode === "all" || element._name === mode)
element.style.backgroundColor = element._backgroundColor;
else
element.style.backgroundColor = "";
@@ -433,9 +454,8 @@
if (!("originalPropertyData" in self))
self.originalPropertyData = self.previousPropertyDataCandidate;
- if (typeof self._highlightMode !== "undefined") {
- WebInspector.domModel.highlightDOMNode(self.node.id, self._highlightMode);
- }
+ if (typeof self._highlightMode !== "undefined")
+ self._node.highlight(self._highlightMode);
if (commitEditor) {
self.dispatchEventToListeners("metrics edited");
diff --git a/Source/devtools/front_end/NetworkManager.js b/Source/devtools/front_end/NetworkManager.js
index 76c0faf..3b883b3 100644
--- a/Source/devtools/front_end/NetworkManager.js
+++ b/Source/devtools/front_end/NetworkManager.js
@@ -193,7 +193,8 @@
networkRequest.timing = response.timing;
if (!this._mimeTypeIsConsistentWithType(networkRequest)) {
- this._manager._target.consoleModel.addMessage(new WebInspector.ConsoleMessage(WebInspector.ConsoleMessage.MessageSource.Network,
+ var consoleModel = this._manager._target.consoleModel;
+ consoleModel.addMessage(new WebInspector.ConsoleMessage(consoleModel.target(), WebInspector.ConsoleMessage.MessageSource.Network,
WebInspector.ConsoleMessage.MessageLevel.Log,
WebInspector.UIString("Resource interpreted as %s but transferred with MIME type %s: \"%s\".", networkRequest.type.title(), networkRequest.mimeType, networkRequest.url),
WebInspector.ConsoleMessage.MessageType.Log,
diff --git a/Source/devtools/front_end/ObjectPopoverHelper.js b/Source/devtools/front_end/ObjectPopoverHelper.js
index 55c322e..a161fb7 100644
--- a/Source/devtools/front_end/ObjectPopoverHelper.js
+++ b/Source/devtools/front_end/ObjectPopoverHelper.js
@@ -62,18 +62,17 @@
_showObjectPopover: function(element, popover)
{
/**
+ * @param {!WebInspector.Target} target
* @param {!Element} anchorElement
* @param {!Element} popoverContentElement
- * @param {?Protocol.Error} error
- * @param {!DebuggerAgent.FunctionDetails} response
+ * @param {?DebuggerAgent.FunctionDetails} response
* @this {WebInspector.ObjectPopoverHelper}
*/
- function didGetDetails(anchorElement, popoverContentElement, error, response)
+ function didGetDetails(target, anchorElement, popoverContentElement, response)
{
- if (error) {
- console.error(error);
+ if (!response)
return;
- }
+
var container = document.createElement("div");
container.className = "inline-block";
@@ -82,7 +81,7 @@
functionName.textContent = response.functionName || WebInspector.UIString("(anonymous function)");
this._linkifier = new WebInspector.Linkifier();
- var rawLocation = /** @type {!WebInspector.DebuggerModel.Location} */ (response.location);
+ var rawLocation = WebInspector.DebuggerModel.Location.fromPayload(target, response.location);
var link = this._linkifier.linkifyRawLocation(rawLocation, "function-location-link");
if (link)
title.appendChild(link);
@@ -117,7 +116,7 @@
popoverContentElement.style.whiteSpace = "pre";
popoverContentElement.textContent = description;
if (result.type === "function") {
- DebuggerAgent.getFunctionDetails(result.objectId, didGetDetails.bind(this, anchorElement, popoverContentElement));
+ result.functionDetails(didGetDetails.bind(this, result.target(), anchorElement, popoverContentElement));
return;
}
if (result.type === "string")
diff --git a/Source/devtools/front_end/ObjectPropertiesSection.js b/Source/devtools/front_end/ObjectPropertiesSection.js
index 6191027..c4e6404 100644
--- a/Source/devtools/front_end/ObjectPropertiesSection.js
+++ b/Source/devtools/front_end/ObjectPropertiesSection.js
@@ -604,16 +604,13 @@
return;
/**
- * @param {?Protocol.Error} error
- * @param {!DebuggerAgent.FunctionDetails} response
+ * @param {?DebuggerAgent.FunctionDetails} response
* @this {WebInspector.FunctionScopeMainTreeElement}
*/
- function didGetDetails(error, response)
+ function didGetDetails(response)
{
- if (error) {
- console.error(error);
+ if (!response)
return;
- }
this.removeChildren();
var scopeChain = response.scopeChain;
@@ -651,10 +648,11 @@
continue;
}
+ var runtimeModel = this._remoteObject.target().runtimeModel;
var scopeRef = isTrueObject ? undefined : new WebInspector.ScopeRef(i, undefined, this._remoteObject.objectId);
- var remoteObject = WebInspector.ScopeRemoteObject.fromPayload(scope.object, scopeRef);
+ var remoteObject = runtimeModel.createScopedObject(scope.object, scopeRef);
if (isTrueObject) {
- var property = WebInspector.RemoteObjectProperty.fromScopeValue(title, remoteObject);
+ var property = runtimeModel.createScopedObject(title, remoteObject);
property.parentObject = null;
this.appendChild(new this.treeOutline.section.treeElementConstructor(property));
} else {
@@ -664,7 +662,7 @@
}
}
- DebuggerAgent.getFunctionDetails(this._remoteObject.objectId, didGetDetails.bind(this));
+ this._remoteObject.functionDetails(didGetDetails.bind(this));
},
__proto__: TreeElement.prototype
diff --git a/Source/devtools/front_end/OverridesSupport.js b/Source/devtools/front_end/OverridesSupport.js
index ea3e5cb..a7088a8 100644
--- a/Source/devtools/front_end/OverridesSupport.js
+++ b/Source/devtools/front_end/OverridesSupport.js
@@ -461,8 +461,8 @@
if (!metrics.isValid())
return;
- var dipWidth = Math.round(metrics.width / metrics.deviceScaleFactor);
- var dipHeight = Math.round(metrics.height / metrics.deviceScaleFactor);
+ var dipWidth = Math.round(metrics.width);
+ var dipHeight = Math.round(metrics.height);
var metricsOverrideEnabled = !!(dipWidth && dipHeight);
// Disable override without checks.
diff --git a/Source/devtools/front_end/OverridesView.js b/Source/devtools/front_end/OverridesView.js
index e86b118..14e6c62 100644
--- a/Source/devtools/front_end/OverridesView.js
+++ b/Source/devtools/front_end/OverridesView.js
@@ -269,49 +269,49 @@
"320x480x1"],
["Apple iPhone 4",
"Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_2_1 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8C148 Safari/6533.18.5",
- "640x960x2"],
+ "320x480x2"],
["Apple iPhone 5",
"Mozilla/5.0 (iPhone; CPU iPhone OS 7_0 like Mac OS X; en-us) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53",
- "640x1136x2"],
+ "320x568x2"],
["BlackBerry Z10",
"Mozilla/5.0 (BB10; Touch) AppleWebKit/537.10+ (KHTML, like Gecko) Version/10.0.9.2372 Mobile Safari/537.10+",
- "768x1280x2"],
+ "384x640x2"],
["BlackBerry Z30",
"Mozilla/5.0 (BB10; Touch) AppleWebKit/537.10+ (KHTML, like Gecko) Version/10.0.9.2372 Mobile Safari/537.10+",
- "720x1280x2"],
+ "360x640x2"],
["Google Nexus 4",
"Mozilla/5.0 (Linux; Android 4.2.1; en-us; Nexus 4 Build/JOP40D) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Mobile Safari/535.19",
- "768x1280x2"],
+ "384x640x2"],
["Google Nexus 5",
"Mozilla/5.0 (Linux; Android 4.2.1; en-us; Nexus 5 Build/JOP40D) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Mobile Safari/535.19",
- "1080x1920x3"],
+ "360x640x3"],
["Google Nexus S",
"Mozilla/5.0 (Linux; U; Android 2.3.4; en-us; Nexus S Build/GRJ22) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1",
- "480x800x1.5"],
+ "320x533x1.5"],
["HTC Evo, Touch HD, Desire HD, Desire",
"Mozilla/5.0 (Linux; U; Android 2.2; en-us; Sprint APA9292KT Build/FRF91) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1",
- "480x800x1.5"],
+ "320x533x1.5"],
["HTC One X, EVO LTE",
"Mozilla/5.0 (Linux; Android 4.0.3; HTC One X Build/IML74K) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19",
- "720x1280x2"],
+ "360x640x2"],
["HTC Sensation, Evo 3D",
"Mozilla/5.0 (Linux; U; Android 4.0.3; en-us; HTC Sensation Build/IML74K) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30",
- "540x960x1.5"],
+ "360x640x1.5"],
["LG Optimus 2X, Optimus 3D, Optimus Black",
"Mozilla/5.0 (Linux; U; Android 2.2; en-us; LG-P990/V08c Build/FRG83) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1 MMS/LG-Android-MMS-V1.0/1.2",
- "480x800x1.5"],
+ "320x533x1.5"],
["LG Optimus G",
"Mozilla/5.0 (Linux; Android 4.0; LG-E975 Build/IMM76L) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Mobile Safari/535.19",
- "768x1280x2"],
+ "384x640x2"],
["LG Optimus LTE, Optimus 4X HD",
"Mozilla/5.0 (Linux; U; Android 2.3; en-us; LG-P930 Build/GRJ90) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1",
- "720x1280x1.7"],
+ "424x753x1.7"],
["LG Optimus One",
"Mozilla/5.0 (Linux; U; Android 2.2.1; en-us; LG-MS690 Build/FRG83) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1",
- "320x480x1.5"],
+ "213x320x1.5"],
["Motorola Defy, Droid, Droid X, Milestone",
"Mozilla/5.0 (Linux; U; Android 2.0; en-us; Milestone Build/ SHOLS_U2_01.03.1) AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0 Mobile Safari/530.17",
- "480x854x1.5"],
+ "320x569x1.5"],
["Motorola Droid 3, Droid 4, Droid Razr, Atrix 4G, Atrix 2",
"Mozilla/5.0 (Linux; U; Android 2.2; en-us; Droid Build/FRG22D) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1",
"540x960x1"],
@@ -323,43 +323,43 @@
"360x640x1"],
["Nokia Lumia 7X0, Lumia 8XX, Lumia 900, N800, N810, N900",
"Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 820)",
- "480x800x1.5"],
+ "320x533x1.5"],
["Samsung Galaxy Note 3",
"Mozilla/5.0 (Linux; U; Android 4.3; en-us; SM-N900T Build/JSS15J) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30",
- "1080x1920x2"],
+ "540x960x2"],
["Samsung Galaxy Note II",
"Mozilla/5.0 (Linux; U; Android 4.1; en-us; GT-N7100 Build/JRO03C) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30",
- "720x1280x2"],
+ "360x640x2"],
["Samsung Galaxy Note",
"Mozilla/5.0 (Linux; U; Android 2.3; en-us; SAMSUNG-SGH-I717 Build/GINGERBREAD) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1",
- "800x1280x2"],
+ "400x640x2"],
["Samsung Galaxy S III, Galaxy Nexus",
"Mozilla/5.0 (Linux; U; Android 4.0; en-us; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30",
- "720x1280x2"],
+ "360x640x2"],
["Samsung Galaxy S, S II, W",
"Mozilla/5.0 (Linux; U; Android 2.1; en-us; GT-I9000 Build/ECLAIR) AppleWebKit/525.10+ (KHTML, like Gecko) Version/3.0.4 Mobile Safari/523.12.2",
- "480x800x1.5"],
+ "320x533x1.5"],
["Samsung Galaxy S4",
"Mozilla/5.0 (Linux; Android 4.2.2; GT-I9505 Build/JDQ39) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.59 Mobile Safari/537.36",
- "1080x1920x3"],
+ "360x640x3"],
["Sony Xperia S, Ion",
"Mozilla/5.0 (Linux; U; Android 4.0; en-us; LT28at Build/6.1.C.1.111) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30",
- "720x1280x2"],
+ "360x640x2"],
["Sony Xperia Sola, U",
"Mozilla/5.0 (Linux; U; Android 2.3; en-us; SonyEricssonST25i Build/6.0.B.1.564) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1",
"480x854x1"],
["Sony Xperia Z, Z1",
"Mozilla/5.0 (Linux; U; Android 4.2; en-us; SonyC6903 Build/14.1.G.1.518) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30",
- "1080x1920x3"],
+ "360x640x3"],
];
WebInspector.OverridesView.DeviceTab._tablets = [
["Amazon Amazon Kindle Fire HD 7\u2033",
"Mozilla/5.0 (Linux; U; Android 2.3.4; en-us; Kindle Fire HD Build/GINGERBREAD) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1",
- "1280x800x1.5"],
+ "853x533x1.5"],
["Amazon Amazon Kindle Fire HD 8.9\u2033",
"Mozilla/5.0 (Linux; U; Android 2.3.4; en-us; Kindle Fire HD Build/GINGERBREAD) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1",
- "1920x1200x1.5"],
+ "1280x800x1.5"],
["Amazon Amazon Kindle Fire",
"Mozilla/5.0 (Linux; U; Android 2.3.4; en-us; Kindle Fire Build/GINGERBREAD) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1",
"1024x600x1"],
@@ -368,19 +368,19 @@
"1024x768x1"],
["Apple iPad 3 / 4",
"Mozilla/5.0 (iPad; CPU OS 7_0 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53",
- "2048x1536x2"],
+ "1024x768x2"],
["BlackBerry PlayBook",
"Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML like Gecko) Version/7.2.1.0 Safari/536.2+",
"1024x600x1"],
["Google Nexus 10",
"Mozilla/5.0 (Linux; Android 4.3; Nexus 10 Build/JSS15Q) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.72 Safari/537.36",
- "2560x1600x2"],
+ "1280x800x2"],
["Google Nexus 7 2",
"Mozilla/5.0 (Linux; Android 4.3; Nexus 7 Build/JSS15Q) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.72 Safari/537.36",
- "1920x1200x2"],
+ "960x600x2"],
["Google Nexus 7",
"Mozilla/5.0 (Linux; Android 4.3; Nexus 7 Build/JSS15Q) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.72 Safari/537.36",
- "1280x800x1.325"],
+ "966x604x1.325"],
["Motorola Xoom, Xyboard",
"Mozilla/5.0 (Linux; U; Android 3.0; en-us; Xoom Build/HRI39) AppleWebKit/525.10 (KHTML, like Gecko) Version/3.0.4 Mobile Safari/523.12.2",
"1280x800x1"],
diff --git a/Source/devtools/front_end/PlatformFontsSidebarPane.js b/Source/devtools/front_end/PlatformFontsSidebarPane.js
index 661538d..e6a9bad 100644
--- a/Source/devtools/front_end/PlatformFontsSidebarPane.js
+++ b/Source/devtools/front_end/PlatformFontsSidebarPane.js
@@ -36,9 +36,6 @@
{
WebInspector.SidebarPane.call(this, WebInspector.UIString("Fonts"));
this.element.classList.add("platform-fonts");
- WebInspector.domModel.addEventListener(WebInspector.DOMModel.Events.AttrModified, this._onNodeChange.bind(this));
- WebInspector.domModel.addEventListener(WebInspector.DOMModel.Events.AttrRemoved, this._onNodeChange.bind(this));
- WebInspector.domModel.addEventListener(WebInspector.DOMModel.Events.CharacterDataModified, this._onNodeChange.bind(this));
this._sectionTitle = document.createElementWithClass("div", "sidebar-separator");
this.element.insertBefore(this._sectionTitle, this.bodyElement);
@@ -64,9 +61,28 @@
return;
}
this._node = node;
+ this._updateTarget(node.target());
this._innerUpdate();
},
+ /**
+ * @param {!WebInspector.Target} target
+ */
+ _updateTarget: function(target)
+ {
+ if (this._target === target)
+ return;
+ if (this._target) {
+ this._target.domModel.removeEventListener(WebInspector.DOMModel.Events.AttrModified, this._onNodeChange, this);
+ this._target.domModel.removeEventListener(WebInspector.DOMModel.Events.AttrRemoved, this._onNodeChange, this);
+ this._target.domModel.removeEventListener(WebInspector.DOMModel.Events.CharacterDataModified, this._onNodeChange, this);
+ }
+ this._target = target;
+ this._target.domModel.addEventListener(WebInspector.DOMModel.Events.AttrModified, this._onNodeChange, this);
+ this._target.domModel.addEventListener(WebInspector.DOMModel.Events.AttrRemoved, this._onNodeChange, this);
+ this._target.domModel.addEventListener(WebInspector.DOMModel.Events.CharacterDataModified, this._onNodeChange, this);
+ },
+
_innerUpdate: function()
{
if (this._innerUpdateTimeout) {
@@ -75,7 +91,7 @@
}
if (!this._node)
return;
- WebInspector.cssModel.getPlatformFontsForNode(this._node.id, this._refreshUI.bind(this, this._node));
+ this._target.cssModel.getPlatformFontsForNode(this._node.id, this._refreshUI.bind(this, this._node));
},
/**
diff --git a/Source/devtools/front_end/PresentationConsoleMessageHelper.js b/Source/devtools/front_end/PresentationConsoleMessageHelper.js
index ccf52d6..e8dcb8c 100644
--- a/Source/devtools/front_end/PresentationConsoleMessageHelper.js
+++ b/Source/devtools/front_end/PresentationConsoleMessageHelper.js
@@ -151,7 +151,7 @@
WebInspector.PresentationConsoleMessage = function(message, rawLocation)
{
this.originalMessage = message;
- this._liveLocation = WebInspector.debuggerModel.createLiveLocation(rawLocation, this._updateLocation.bind(this));
+ this._liveLocation = rawLocation.createLiveLocation(this._updateLocation.bind(this));
}
WebInspector.PresentationConsoleMessage.prototype = {
diff --git a/Source/devtools/front_end/ProfileDataGridTree.js b/Source/devtools/front_end/ProfileDataGridTree.js
index 8ed6aa1..b8938f5 100644
--- a/Source/devtools/front_end/ProfileDataGridTree.js
+++ b/Source/devtools/front_end/ProfileDataGridTree.js
@@ -107,7 +107,7 @@
if (this.profileNode.scriptId !== "0") {
var lineNumber = this.profileNode.lineNumber ? this.profileNode.lineNumber - 1 : 0;
var columnNumber = this.profileNode.columnNumber ? this.profileNode.columnNumber - 1 : 0;
- var location = new WebInspector.DebuggerModel.Location(this.profileNode.scriptId, lineNumber, columnNumber);
+ var location = new WebInspector.DebuggerModel.Location(/** @type {!WebInspector.Target} */ (WebInspector.targetManager.activeTarget()), this.profileNode.scriptId, lineNumber, columnNumber);
var urlElement = this.tree.profileView._linkifier.linkifyRawLocation(location, "profile-node-file");
if (!urlElement)
urlElement = this.tree.profileView._linkifier.linkifyLocation(this.profileNode.url, lineNumber, columnNumber, "profile-node-file");
diff --git a/Source/devtools/front_end/PropertiesSidebarPane.js b/Source/devtools/front_end/PropertiesSidebarPane.js
index 7ee32a5..fce1759 100644
--- a/Source/devtools/front_end/PropertiesSidebarPane.js
+++ b/Source/devtools/front_end/PropertiesSidebarPane.js
@@ -48,7 +48,7 @@
return;
}
- WebInspector.RemoteObject.resolveNode(node, WebInspector.PropertiesSidebarPane._objectGroupName, nodeResolved.bind(this));
+ node.resolveToObject(WebInspector.PropertiesSidebarPane._objectGroupName, nodeResolved.bind(this));
/**
* @this {WebInspector.PropertiesSidebarPane}
diff --git a/Source/devtools/front_end/RemoteObject.js b/Source/devtools/front_end/RemoteObject.js
index 71ea9eb..2a5420e 100644
--- a/Source/devtools/front_end/RemoteObject.js
+++ b/Source/devtools/front_end/RemoteObject.js
@@ -105,27 +105,23 @@
throw "Not implemented";
},
+ /**
+ * @return {!WebInspector.Target}
+ */
target: function()
{
- throw "Not implemented";
+ throw new Error("Target-less object");
+ },
+
+ /**
+ * @param {function(?DebuggerAgent.FunctionDetails)} callback
+ */
+ functionDetails: function(callback)
+ {
+ callback(null);
}
}
-
-/**
- * @param {number|string|boolean} value
- * @param {!WebInspector.Target=} target
- * @return {!WebInspector.RemoteObject}
- */
-WebInspector.RemoteObject.fromPrimitiveValue = function(value, target)
-{
- //FIXME: we should always pass non-undefined target
- if (!target)
- target = WebInspector.targetManager.mainTarget();
-
- return new WebInspector.RemoteObjectImpl(target, undefined, typeof value, undefined, value);
-}
-
/**
* @param {*} value
* @return {!WebInspector.RemoteObject}
@@ -136,46 +132,6 @@
}
/**
- * @param {!WebInspector.DOMNode} node
- * @param {string} objectGroup
- * @param {function(?WebInspector.RemoteObject)} callback
- */
-WebInspector.RemoteObject.resolveNode = function(node, objectGroup, callback)
-{
- /**
- * @param {?Protocol.Error} error
- * @param {!RuntimeAgent.RemoteObject} object
- */
- function mycallback(error, object)
- {
- if (!callback)
- return;
-
- if (error || !object)
- callback(null);
- else
- callback(WebInspector.RemoteObject.fromPayload(object));
- }
- DOMAgent.resolveNode(node.id, objectGroup, mycallback);
-}
-
-/**
- * @param {!RuntimeAgent.RemoteObject} payload
- * @param {!WebInspector.Target=} target
- * @return {!WebInspector.RemoteObject}
- */
-WebInspector.RemoteObject.fromPayload = function(payload, target)
-{
- //FIXME: we should always pass non-undefined target
- if (!target)
- target = WebInspector.targetManager.mainTarget();
-
- console.assert(typeof payload === "object", "Remote object payload should only be an object");
-
- return new WebInspector.RemoteObjectImpl(target, payload.objectId, payload.type, payload.subtype, payload.value, payload.description, payload.preview);
-}
-
-/**
* @param {!WebInspector.RemoteObject} remoteObject
* @return {string}
*/
@@ -353,6 +309,7 @@
* @param {?Protocol.Error} error
* @param {!Array.<!RuntimeAgent.PropertyDescriptor>} properties
* @param {!Array.<!RuntimeAgent.InternalPropertyDescriptor>=} internalProperties
+ * @this {WebInspector.RemoteObjectImpl}
*/
function remoteObjectBinder(error, properties, internalProperties)
{
@@ -363,7 +320,18 @@
var result = [];
for (var i = 0; properties && i < properties.length; ++i) {
var property = properties[i];
- result.push(new WebInspector.RemoteObjectProperty(property.name, null, property));
+ var propertyValue = property.value ? this._target.runtimeModel.createRemoteObject(property.value) : null;
+ var remoteProperty = new WebInspector.RemoteObjectProperty(property.name, propertyValue,
+ !!property.enumerable, !!property.writable, !!property.isOwn, !!property.wasThrown);
+
+ if (typeof property.value === "undefined") {
+ if (property.get && property.get.type !== "undefined")
+ remoteProperty.getter = this._target.runtimeModel.createRemoteObject(property.get);
+ if (property.set && property.set.type !== "undefined")
+ remoteProperty.setter = this._target.runtimeModel.createRemoteObject(property.set);
+ }
+
+ result.push(remoteProperty);
}
var internalPropertiesResult = null;
if (internalProperties) {
@@ -372,12 +340,12 @@
var property = internalProperties[i];
if (!property.value)
continue;
- internalPropertiesResult.push(new WebInspector.RemoteObjectProperty(property.name, WebInspector.RemoteObject.fromPayload(property.value)));
+ internalPropertiesResult.push(new WebInspector.RemoteObjectProperty(property.name, this._target.runtimeModel.createRemoteObject(property.value)));
}
}
callback(result, internalPropertiesResult);
}
- this._runtimeAgent.getProperties(this._objectId, ownProperties, accessorPropertiesOnly, remoteObjectBinder);
+ this._runtimeAgent.getProperties(this._objectId, ownProperties, accessorPropertiesOnly, remoteObjectBinder.bind(this));
},
/**
@@ -477,6 +445,7 @@
* @param {?Protocol.Error} error
* @param {!RuntimeAgent.RemoteObject} result
* @param {boolean=} wasThrown
+ * @this {WebInspector.RemoteObjectImpl}
*/
function mycallback(error, result, wasThrown)
{
@@ -485,10 +454,10 @@
if (error)
callback(null, false);
else
- callback(WebInspector.RemoteObject.fromPayload(result), wasThrown);
+ callback(this.target().runtimeModel.createRemoteObject(result), wasThrown);
}
- this._runtimeAgent.callFunctionOn(this._objectId, functionDeclaration.toString(), args, true, undefined, undefined, mycallback);
+ this._runtimeAgent.callFunctionOn(this._objectId, functionDeclaration.toString(), args, true, undefined, undefined, mycallback.bind(this));
},
/**
@@ -540,6 +509,14 @@
return this._target;
},
+ /**
+ * @param {function(?DebuggerAgent.FunctionDetails)} callback
+ */
+ functionDetails: function(callback)
+ {
+ this._target.debuggerModel.functionDetails(this, callback)
+ },
+
__proto__: WebInspector.RemoteObject.prototype
};
@@ -632,24 +609,6 @@
this._debuggerAgent = target.debuggerAgent();
};
-/**
- * @param {!RuntimeAgent.RemoteObject} payload
- * @param {!WebInspector.ScopeRef=} scopeRef
- * @param {!WebInspector.Target=} target
- * @return {!WebInspector.RemoteObject}
- */
-WebInspector.ScopeRemoteObject.fromPayload = function(payload, scopeRef, target)
-{
- //FIXME: we should always pass non-undefined target
- if (!target)
- target = WebInspector.targetManager.mainTarget();
-
- if (scopeRef)
- return new WebInspector.ScopeRemoteObject(target, payload.objectId, scopeRef, payload.type, payload.subtype, payload.value, payload.description, payload.preview);
- else
- return new WebInspector.RemoteObjectImpl(target, payload.objectId, payload.type, payload.subtype, payload.value, payload.description, payload.preview);
-}
-
WebInspector.ScopeRemoteObject.prototype = {
/**
* @param {boolean} ownProperties
@@ -709,7 +668,7 @@
if (this._savedScopeProperties) {
for (var i = 0; i < this._savedScopeProperties.length; i++) {
if (this._savedScopeProperties[i].name === name)
- this._savedScopeProperties[i].value = WebInspector.RemoteObject.fromPayload(result);
+ this._savedScopeProperties[i].value = this._target.runtimeModel.createRemoteObject(result);
}
}
callback();
@@ -737,29 +696,20 @@
* @constructor
* @param {string} name
* @param {?WebInspector.RemoteObject} value
- * @param {!RuntimeAgent.PropertyDescriptor=} descriptor
+ * @param {boolean=} enumerable
+ * @param {boolean=} writable
+ * @param {boolean=} isOwn
+ * @param {boolean=} wasThrown
*/
-WebInspector.RemoteObjectProperty = function(name, value, descriptor)
+WebInspector.RemoteObjectProperty = function(name, value, enumerable, writable, isOwn, wasThrown)
{
this.name = name;
- this.enumerable = descriptor ? !!descriptor.enumerable : true;
- this.writable = descriptor ? !!descriptor.writable : true;
-
- if (value === null && descriptor) {
- if (descriptor.value)
- this.value = WebInspector.RemoteObject.fromPayload(descriptor.value)
- if (descriptor.get && descriptor.get.type !== "undefined")
- this.getter = WebInspector.RemoteObject.fromPayload(descriptor.get);
- if (descriptor.set && descriptor.set.type !== "undefined")
- this.setter = WebInspector.RemoteObject.fromPayload(descriptor.set);
- } else {
- this.value = value;
- }
-
- if (descriptor) {
- this.isOwn = descriptor.isOwn;
- this.wasThrown = !!descriptor.wasThrown;
- }
+ if (value !== null)
+ this.value = value;
+ this.enumerable = typeof enumerable !== "undefined" ? enumerable : true;
+ this.writable = typeof writable !== "undefined" ? writable : true;
+ this.isOwn = !!isOwn;
+ this.wasThrown = !!wasThrown;
}
WebInspector.RemoteObjectProperty.prototype = {
@@ -772,28 +722,6 @@
}
};
-/**
- * @param {string} name
- * @param {string} value
- * @return {!WebInspector.RemoteObjectProperty}
- */
-WebInspector.RemoteObjectProperty.fromPrimitiveValue = function(name, value)
-{
- return new WebInspector.RemoteObjectProperty(name, WebInspector.RemoteObject.fromPrimitiveValue(value));
-}
-
-/**
- * @param {string} name
- * @param {!WebInspector.RemoteObject} value
- * @return {!WebInspector.RemoteObjectProperty}
- */
-WebInspector.RemoteObjectProperty.fromScopeValue = function(name, value)
-{
- var result = new WebInspector.RemoteObjectProperty(name, value);
- result.writable = false;
- return result;
-}
-
// Below is a wrapper around a local object that implements the RemoteObject interface,
// which can be used by the UI code (primarily ObjectPropertiesSection).
// Note that only JSON-compliant objects are currently supported, as there's no provision
diff --git a/Source/devtools/front_end/ResourceScriptMapping.js b/Source/devtools/front_end/ResourceScriptMapping.js
index 4e10328..f46ef0e 100644
--- a/Source/devtools/front_end/ResourceScriptMapping.js
+++ b/Source/devtools/front_end/ResourceScriptMapping.js
@@ -52,7 +52,7 @@
rawLocationToUILocation: function(rawLocation)
{
var debuggerModelLocation = /** @type {!WebInspector.DebuggerModel.Location} */ (rawLocation);
- var script = this._debuggerModel.scriptForId(debuggerModelLocation.scriptId);
+ var script = debuggerModelLocation.script();
var uiSourceCode = this._workspaceUISourceCodeForScript(script);
if (!uiSourceCode)
return null;
@@ -172,7 +172,7 @@
for (var i = 0; i < scripts.length; ++i)
scripts[i].updateLocations();
uiSourceCode.setSourceMapping(this);
- this._boundURLs.put(uiSourceCode.url);
+ this._boundURLs.add(uiSourceCode.url);
},
/**
diff --git a/Source/devtools/front_end/RuntimeModel.js b/Source/devtools/front_end/RuntimeModel.js
index 6f42717..aa376b6 100644
--- a/Source/devtools/front_end/RuntimeModel.js
+++ b/Source/devtools/front_end/RuntimeModel.js
@@ -30,16 +30,17 @@
/**
* @constructor
- * @extends {WebInspector.Object}
+ * @extends {WebInspector.TargetAwareObject}
* @param {!WebInspector.Target} target
*/
WebInspector.RuntimeModel = function(target)
{
+ WebInspector.TargetAwareObject.call(this, target);
+
target.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameAdded, this._frameAdded, this);
target.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameNavigated, this._frameNavigated, this);
target.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameDetached, this._frameDetached, this);
target.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.CachedResourcesLoaded, this._didLoadCachedResources, this);
- this._target = target;
this._debuggerModel = target.debuggerModel;
this._agent = target.runtimeAgent();
this._contextListById = {};
@@ -57,7 +58,7 @@
*/
addWorkerContextList: function(url)
{
- console.assert(this._target.isWorkerTarget(), "Worker context list was added in a non-worker target");
+ console.assert(this.target().isWorkerTarget(), "Worker context list was added in a non-worker target");
var fakeContextList = new WebInspector.WorkerExecutionContextList("worker", url);
this._addContextList(fakeContextList);
var fakeExecutionContext = new WebInspector.ExecutionContext(undefined, url, true);
@@ -102,7 +103,7 @@
*/
_frameAdded: function(event)
{
- console.assert(!this._target.isWorkerTarget() ,"Frame was added in a worker target.t");
+ console.assert(!this.target().isWorkerTarget() ,"Frame was added in a worker target.t");
var frame = /** @type {!WebInspector.ResourceTreeFrame} */ (event.data);
var contextList = new WebInspector.FrameExecutionContextList(frame);
this._addContextList(contextList);
@@ -119,7 +120,7 @@
*/
_frameNavigated: function(event)
{
- console.assert(!this._target.isWorkerTarget() ,"Frame was navigated in worker's target");
+ console.assert(!this.target().isWorkerTarget() ,"Frame was navigated in worker's target");
var frame = /** @type {!WebInspector.ResourceTreeFrame} */ (event.data);
var context = this._contextListById[frame.id];
if (context)
@@ -131,7 +132,7 @@
*/
_frameDetached: function(event)
{
- console.assert(!this._target.isWorkerTarget() ,"Frame was detached in worker's target");
+ console.assert(!this.target().isWorkerTarget() ,"Frame was detached in worker's target");
var frame = /** @type {!WebInspector.ResourceTreeFrame} */ (event.data);
var context = this._contextListById[frame.id];
if (!context)
@@ -142,7 +143,7 @@
_didLoadCachedResources: function()
{
- this._target.registerRuntimeDispatcher(new WebInspector.RuntimeDispatcher(this));
+ this.target().registerRuntimeDispatcher(new WebInspector.RuntimeDispatcher(this));
this._agent.enable();
},
@@ -190,7 +191,7 @@
if (returnByValue)
callback(null, !!wasThrown, wasThrown ? null : result);
else
- callback(WebInspector.RemoteObject.fromPayload(result, this._target), !!wasThrown);
+ callback(this.target().runtimeModel.createRemoteObject(result), !!wasThrown);
}
this._agent.evaluate(expression, objectGroup, includeCommandLineAPI, doNotPauseOnExceptionsAndMuteConsole, this._currentExecutionContext ? this._currentExecutionContext.id : undefined, returnByValue, generatePreview, evalCallback.bind(this));
},
@@ -371,7 +372,48 @@
completionsReadyCallback(results);
},
- __proto__: WebInspector.Object.prototype
+ /**
+ * @return {!WebInspector.RemoteObject}
+ */
+ createRemoteObject: function(payload)
+ {
+ console.assert(typeof payload === "object", "Remote object payload should only be an object");
+ return new WebInspector.RemoteObjectImpl(this.target(), payload.objectId, payload.type, payload.subtype, payload.value, payload.description, payload.preview);
+ },
+
+ /**
+ * @param {number|string|boolean} value
+ * @return {!WebInspector.RemoteObject}
+ */
+ createRemoteObjectFromPrimitiveValue: function(value)
+ {
+ return new WebInspector.RemoteObjectImpl(this.target(), undefined, typeof value, undefined, value);
+ },
+
+ /**
+ * @param {string} name
+ * @param {string} value
+ * @return {!WebInspector.RemoteObjectProperty}
+ */
+ createRemotePropertyFromPrimitiveValue: function(name, value)
+ {
+ return new WebInspector.RemoteObjectProperty(name, this.createRemoteObjectFromPrimitiveValue(value));
+ },
+
+ /**
+ * @param {!RuntimeAgent.RemoteObject} payload
+ * @param {!WebInspector.ScopeRef=} scopeRef
+ * @return {!WebInspector.RemoteObject}
+ */
+ createScopedObject: function(payload, scopeRef)
+ {
+ if (scopeRef)
+ return new WebInspector.ScopeRemoteObject(this.target(), payload.objectId, scopeRef, payload.type, payload.subtype, payload.value, payload.description, payload.preview);
+ else
+ return new WebInspector.RemoteObjectImpl(this.target(), payload.objectId, payload.type, payload.subtype, payload.value, payload.description, payload.preview);
+ },
+
+ __proto__: WebInspector.TargetAwareObject.prototype
}
/**
diff --git a/Source/devtools/front_end/SASSSourceMapping.js b/Source/devtools/front_end/SASSSourceMapping.js
index ca223a5..bd400ab 100644
--- a/Source/devtools/front_end/SASSSourceMapping.js
+++ b/Source/devtools/front_end/SASSSourceMapping.js
@@ -561,7 +561,7 @@
uiLocationToRawLocation: function(uiSourceCode, lineNumber, columnNumber)
{
// FIXME: Implement this when ui -> raw mapping has clients.
- return new WebInspector.CSSLocation(uiSourceCode.url || "", lineNumber, columnNumber);
+ return new WebInspector.CSSLocation(this._cssModel.target(), uiSourceCode.url || "", lineNumber, columnNumber);
},
/**
diff --git a/Source/devtools/front_end/ScopeChainSidebarPane.js b/Source/devtools/front_end/ScopeChainSidebarPane.js
index 0897b19..feb19e5 100644
--- a/Source/devtools/front_end/ScopeChainSidebarPane.js
+++ b/Source/devtools/front_end/ScopeChainSidebarPane.js
@@ -80,17 +80,19 @@
title = WebInspector.UIString("Local");
emptyPlaceholder = WebInspector.UIString("No Variables");
subtitle = undefined;
- if (callFrame.this)
- extraProperties.push(new WebInspector.RemoteObjectProperty("this", WebInspector.RemoteObject.fromPayload(callFrame.this)));
+ var thisObject = callFrame.thisObject();
+ if (thisObject)
+ extraProperties.push(new WebInspector.RemoteObjectProperty("this", thisObject));
if (i == 0) {
var details = WebInspector.debuggerModel.debuggerPausedDetails();
- var exception = details.reason === WebInspector.DebuggerModel.BreakReason.Exception ? details.auxData : 0;
- if (exception && !callFrame.isAsync()) {
- var exceptionObject = /** @type {!RuntimeAgent.RemoteObject} */ (exception);
- extraProperties.push(new WebInspector.RemoteObjectProperty("<exception>", WebInspector.RemoteObject.fromPayload(exceptionObject)));
+ if (!callFrame.isAsync()) {
+ var exception = details.exception();
+ if (exception)
+ extraProperties.push(new WebInspector.RemoteObjectProperty("<exception>", exception));
}
- if (callFrame.returnValue)
- extraProperties.push(new WebInspector.RemoteObjectProperty("<return>", WebInspector.RemoteObject.fromPayload(callFrame.returnValue)));
+ var returnValue = callFrame.returnValue();
+ if (returnValue)
+ extraProperties.push(new WebInspector.RemoteObjectProperty("<return>", returnValue));
}
declarativeScope = true;
break;
@@ -119,7 +121,7 @@
subtitle = undefined;
var scopeRef = declarativeScope ? new WebInspector.ScopeRef(i, callFrame.id, undefined) : undefined;
- var scopeObject = WebInspector.ScopeRemoteObject.fromPayload(scope.object, scopeRef);
+ var scopeObject = callFrame.target().runtimeModel.createScopedObject(scope.object, scopeRef);
var section = new WebInspector.ObjectPropertiesSection(scopeObject, title, subtitle, emptyPlaceholder, true, extraProperties, WebInspector.ScopeVariableTreeElement);
section.editInSelectedCallFrameWhenPaused = true;
section.pane = this;
diff --git a/Source/devtools/front_end/Script.js b/Source/devtools/front_end/Script.js
index 0aba666..0bd86ce 100644
--- a/Source/devtools/front_end/Script.js
+++ b/Source/devtools/front_end/Script.js
@@ -25,8 +25,9 @@
/**
* @constructor
- * @extends {WebInspector.Object}
+ * @extends {WebInspector.TargetAwareObject}
* @implements {WebInspector.ContentProvider}
+ * @param {!WebInspector.Target} target
* @param {string} scriptId
* @param {string} sourceURL
* @param {number} startLine
@@ -37,8 +38,9 @@
* @param {string=} sourceMapURL
* @param {boolean=} hasSourceURL
*/
-WebInspector.Script = function(scriptId, sourceURL, startLine, startColumn, endLine, endColumn, isContentScript, sourceMapURL, hasSourceURL)
+WebInspector.Script = function(target, scriptId, sourceURL, startLine, startColumn, endLine, endColumn, isContentScript, sourceMapURL, hasSourceURL)
{
+ WebInspector.TargetAwareObject.call(this, target);
this.scriptId = scriptId;
this.sourceURL = sourceURL;
this.lineOffset = startLine;
@@ -225,7 +227,7 @@
rawLocationToUILocation: function(lineNumber, columnNumber)
{
var uiLocation;
- var rawLocation = new WebInspector.DebuggerModel.Location(this.scriptId, lineNumber, columnNumber || 0);
+ var rawLocation = new WebInspector.DebuggerModel.Location(this.target(), this.scriptId, lineNumber, columnNumber || 0);
for (var i = this._sourceMappings.length - 1; !uiLocation && i >= 0; --i)
uiLocation = this._sourceMappings[i].rawLocationToUILocation(rawLocation);
console.assert(uiLocation, "Script raw location can not be mapped to any ui location.");
@@ -272,7 +274,7 @@
return location;
},
- __proto__: WebInspector.Object.prototype
+ __proto__: WebInspector.TargetAwareObject.prototype
}
/**
diff --git a/Source/devtools/front_end/ScriptFormatterEditorAction.js b/Source/devtools/front_end/ScriptFormatterEditorAction.js
index 3f885e7..0eb81d8 100644
--- a/Source/devtools/front_end/ScriptFormatterEditorAction.js
+++ b/Source/devtools/front_end/ScriptFormatterEditorAction.js
@@ -29,7 +29,7 @@
rawLocationToUILocation: function(rawLocation)
{
var debuggerModelLocation = /** @type {!WebInspector.DebuggerModel.Location} */ (rawLocation);
- var script = this._debuggerModel.scriptForId(debuggerModelLocation.scriptId);
+ var script = debuggerModelLocation.script();
var uiSourceCode = this._uiSourceCodes.get(script);
if (!uiSourceCode)
return null;
@@ -85,8 +85,8 @@
if (uiSourceCode.contentType() === WebInspector.resourceTypes.Document)
return this._debuggerModel.scriptsForSourceURL(uiSourceCode.url).filter(isInlineScript);
if (uiSourceCode.contentType() === WebInspector.resourceTypes.Script) {
- var rawLocation = uiSourceCode.uiLocationToRawLocation(0, 0);
- return rawLocation ? [this._debuggerModel.scriptForId(rawLocation.scriptId)] : [];
+ var rawLocation = /** @type {!WebInspector.DebuggerModel.Location} */ (uiSourceCode.uiLocationToRawLocation(0, 0));
+ return rawLocation ? [rawLocation.script()] : [];
}
return [];
},
diff --git a/Source/devtools/front_end/ScriptSnippetModel.js b/Source/devtools/front_end/ScriptSnippetModel.js
index 993c8d1..7e5e46e 100644
--- a/Source/devtools/front_end/ScriptSnippetModel.js
+++ b/Source/devtools/front_end/ScriptSnippetModel.js
@@ -223,6 +223,7 @@
if (!scriptId) {
var consoleMessage = new WebInspector.ConsoleMessage(
+ WebInspector.console.target(),
WebInspector.ConsoleMessage.MessageSource.JS,
WebInspector.ConsoleMessage.MessageLevel.Error,
syntaxErrorMessage || "");
@@ -269,7 +270,7 @@
_printRunScriptResult: function(result, wasThrown)
{
var level = (wasThrown ? WebInspector.ConsoleMessage.MessageLevel.Error : WebInspector.ConsoleMessage.MessageLevel.Log);
- var message = new WebInspector.ConsoleMessage(
+ var message = new WebInspector.ConsoleMessage(WebInspector.console.target(),
WebInspector.ConsoleMessage.MessageSource.JS,
level,
"",
diff --git a/Source/devtools/front_end/Settings.js b/Source/devtools/front_end/Settings.js
index 9e820cf..b6f4f28 100644
--- a/Source/devtools/front_end/Settings.js
+++ b/Source/devtools/front_end/Settings.js
@@ -343,6 +343,7 @@
this.heapSnapshotStatistics = this._createExperiment("heapSnapshotStatistics", "Show memory breakdown statistics in heap snapshots");
this.timelineNoLiveUpdate = this._createExperiment("timelineNoLiveUpdate", "Timeline w/o live update");
this.powerProfiler = this._createExperiment("powerProfiler", "Enable power mode in Timeline");
+ this.timelineTracingMode = this._createExperiment("timelineTracingMode", "Enable Tracing mode in Timeline");
this._cleanUpSetting();
}
@@ -483,7 +484,7 @@
{
}
-WebInspector.VersionController.currentVersion = 7;
+WebInspector.VersionController.currentVersion = 8;
WebInspector.VersionController.prototype = {
updateVersion: function()
@@ -646,6 +647,30 @@
}
},
+ _updateVersionFrom7To8: function()
+ {
+ var settingName = "deviceMetrics";
+ if (!window.localStorage || !(settingName in window.localStorage))
+ return;
+ var setting = WebInspector.settings.createSetting(settingName, undefined);
+ var value = setting.get();
+ if (!value)
+ return;
+
+ var components = value.split("x");
+ if (components.length >= 3) {
+ var width = parseInt(components[0], 10);
+ var height = parseInt(components[1], 10);
+ var deviceScaleFactor = parseFloat(components[2]);
+ if (deviceScaleFactor) {
+ components[0] = "" + Math.round(width / deviceScaleFactor);
+ components[1] = "" + Math.round(height / deviceScaleFactor);
+ }
+ }
+ value = components.join("x");
+ setting.set(value);
+ },
+
/**
* @param {!WebInspector.Setting} breakpointsSetting
* @param {number} maxBreakpointsCount
diff --git a/Source/devtools/front_end/SettingsScreen.js b/Source/devtools/front_end/SettingsScreen.js
index 8451756..70a2de3 100644
--- a/Source/devtools/front_end/SettingsScreen.js
+++ b/Source/devtools/front_end/SettingsScreen.js
@@ -62,20 +62,6 @@
}
/**
- * @param {string} text
- * @return {?string}
- */
-WebInspector.SettingsScreen.regexValidator = function(text)
-{
- var regex;
- try {
- regex = new RegExp(text);
- } catch (e) {
- }
- return regex ? null : WebInspector.UIString("Invalid pattern");
-}
-
-/**
* @param {number} min
* @param {number} max
* @param {string} text
@@ -205,60 +191,6 @@
return p;
},
- /**
- * @param {string} label
- * @param {!WebInspector.Setting} setting
- * @param {boolean} numeric
- * @param {number=} maxLength
- * @param {string=} width
- * @param {function(string):?string=} validatorCallback
- */
- _createInputSetting: function(label, setting, numeric, maxLength, width, validatorCallback)
- {
- var p = document.createElement("p");
- var labelElement = p.createChild("label");
- labelElement.textContent = label;
- var inputElement = p.createChild("input");
- inputElement.value = setting.get();
- inputElement.type = "text";
- if (numeric)
- inputElement.className = "numeric";
- if (maxLength)
- inputElement.maxLength = maxLength;
- if (width)
- inputElement.style.width = width;
- if (validatorCallback) {
- var errorMessageLabel = p.createChild("div");
- errorMessageLabel.classList.add("field-error-message");
- errorMessageLabel.style.color = "DarkRed";
- inputElement.oninput = function()
- {
- var error = validatorCallback(inputElement.value);
- if (!error)
- error = "";
- errorMessageLabel.textContent = error;
- };
- }
-
- function onBlur()
- {
- setting.set(numeric ? Number(inputElement.value) : inputElement.value);
- }
- inputElement.addEventListener("blur", onBlur, false);
-
- return p;
- },
-
- _createCustomSetting: function(name, element)
- {
- var p = document.createElement("p");
- var fieldsetElement = document.createElement("fieldset");
- fieldsetElement.createChild("label").textContent = name;
- fieldsetElement.appendChild(element);
- p.appendChild(fieldsetElement);
- return p;
- },
-
__proto__: WebInspector.VBox.prototype
}
@@ -270,88 +202,9 @@
{
WebInspector.SettingsTab.call(this, WebInspector.UIString("General"), "general-tab-content");
- var p = this._appendSection();
- p.appendChild(WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("Disable cache (while DevTools is open)"), WebInspector.settings.cacheDisabled));
- var disableJSElement = WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("Disable JavaScript"), WebInspector.settings.javaScriptDisabled);
- p.appendChild(disableJSElement);
- WebInspector.settings.javaScriptDisabled.addChangeListener(this._javaScriptDisabledChanged, this);
- this._disableJSCheckbox = disableJSElement.getElementsByTagName("input")[0];
- var disableJSInfoParent = this._disableJSCheckbox.parentElement.createChild("span", "monospace");
- this._disableJSInfo = disableJSInfoParent.createChild("span", "object-info-state-note hidden");
- this._disableJSInfo.title = WebInspector.UIString("JavaScript is blocked on the inspected page (may be disabled in browser settings).");
+ this._populateSectionsFromExtensions();
- WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.MainFrameNavigated, this._updateScriptDisabledCheckbox, this);
- this._updateScriptDisabledCheckbox();
-
- p = this._appendSection(WebInspector.UIString("Appearance"));
- var splitVerticallyTitle = WebInspector.UIString("Split panels vertically when docked to %s", WebInspector.experimentsSettings.dockToLeft.isEnabled() ? "left or right" : "right");
- p.appendChild(WebInspector.SettingsUI.createSettingCheckbox(splitVerticallyTitle, WebInspector.settings.splitVerticallyWhenDockedToRight));
- var panelShortcutTitle = WebInspector.UIString("Enable %s + 1-9 shortcut to switch panels", WebInspector.isMac() ? "Cmd" : "Ctrl");
- p.appendChild(WebInspector.SettingsUI.createSettingCheckbox(panelShortcutTitle, WebInspector.settings.shortcutPanelSwitch));
-
- p = this._appendSection(WebInspector.UIString("Elements"));
- var colorFormatElement = this._createSelectSetting(WebInspector.UIString("Color format"), [
- [ WebInspector.UIString("As authored"), WebInspector.Color.Format.Original ],
- [ "HEX: #DAC0DE", WebInspector.Color.Format.HEX ],
- [ "RGB: rgb(128, 255, 255)", WebInspector.Color.Format.RGB ],
- [ "HSL: hsl(300, 80%, 90%)", WebInspector.Color.Format.HSL ]
- ], WebInspector.settings.colorFormat);
- p.appendChild(colorFormatElement);
- p.appendChild(WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("Show user agent styles"), WebInspector.settings.showUserAgentStyles));
- p.appendChild(WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("Show user agent shadow DOM"), WebInspector.settings.showUAShadowDOM));
- p.appendChild(WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("Word wrap"), WebInspector.settings.domWordWrap));
- p.appendChild(WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("Show rulers"), WebInspector.settings.showMetricsRulers));
-
- p = this._appendSection(WebInspector.UIString("Sources"));
- p.appendChild(WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("Search in content scripts"), WebInspector.settings.searchInContentScripts));
- p.appendChild(WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("Enable JavaScript source maps"), WebInspector.settings.jsSourceMapsEnabled));
-
- var checkbox = WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("Enable CSS source maps"), WebInspector.settings.cssSourceMapsEnabled);
- p.appendChild(checkbox);
- var fieldset = WebInspector.SettingsUI.createSettingFieldset(WebInspector.settings.cssSourceMapsEnabled);
- var autoReloadCSSCheckbox = fieldset.createChild("input");
- fieldset.appendChild(WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("Auto-reload generated CSS"), WebInspector.settings.cssReloadEnabled, false, autoReloadCSSCheckbox));
- checkbox.appendChild(fieldset);
-
- var indentationElement = this._createSelectSetting(WebInspector.UIString("Default indentation"), [
- [ WebInspector.UIString("2 spaces"), WebInspector.TextUtils.Indent.TwoSpaces ],
- [ WebInspector.UIString("4 spaces"), WebInspector.TextUtils.Indent.FourSpaces ],
- [ WebInspector.UIString("8 spaces"), WebInspector.TextUtils.Indent.EightSpaces ],
- [ WebInspector.UIString("Tab character"), WebInspector.TextUtils.Indent.TabCharacter ]
- ], WebInspector.settings.textEditorIndent);
- p.appendChild(indentationElement);
- p.appendChild(WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("Detect indentation"), WebInspector.settings.textEditorAutoDetectIndent));
- p.appendChild(WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("Autocompletion"), WebInspector.settings.textEditorAutocompletion));
- p.appendChild(WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("Bracket matching"), WebInspector.settings.textEditorBracketMatching));
- p.appendChild(WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("Show whitespace characters"), WebInspector.settings.showWhitespacesInEditor));
- if (WebInspector.experimentsSettings.frameworksDebuggingSupport.isEnabled()) {
- checkbox = WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("Skip stepping through sources with particular names"), WebInspector.settings.skipStackFramesSwitch);
- fieldset = WebInspector.SettingsUI.createSettingFieldset(WebInspector.settings.skipStackFramesSwitch);
- fieldset.appendChild(this._createInputSetting(WebInspector.UIString("Pattern"), WebInspector.settings.skipStackFramesPattern, false, 1000, "100px", WebInspector.SettingsScreen.regexValidator));
- checkbox.appendChild(fieldset);
- p.appendChild(checkbox);
- }
- WebInspector.settings.skipStackFramesSwitch.addChangeListener(this._skipStackFramesSwitchOrPatternChanged, this);
- WebInspector.settings.skipStackFramesPattern.addChangeListener(this._skipStackFramesSwitchOrPatternChanged, this);
-
- p = this._appendSection(WebInspector.UIString("Profiler"));
- p.appendChild(WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("Show advanced heap snapshot properties"), WebInspector.settings.showAdvancedHeapSnapshotProperties));
- p.appendChild(WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("High resolution CPU profiling"), WebInspector.settings.highResolutionCpuProfiling));
-
- p = this._appendSection(WebInspector.UIString("Console"));
- p.appendChild(WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("Log XMLHttpRequests"), WebInspector.settings.monitoringXHREnabled));
- p.appendChild(WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("Preserve log upon navigation"), WebInspector.settings.preserveConsoleLog));
- p.appendChild(WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("Show timestamps"), WebInspector.settings.consoleTimestampsEnabled));
-
- if (WebInspector.openAnchorLocationRegistry.handlerNames.length > 0) {
- var handlerSelector = new WebInspector.HandlerSelector(WebInspector.openAnchorLocationRegistry);
- p = this._appendSection(WebInspector.UIString("Extensions"));
- p.appendChild(this._createCustomSetting(WebInspector.UIString("Open links in"), handlerSelector.element));
- }
-
- p = this._appendSection();
-
- var restoreDefaults = p.createChild("input", "settings-tab-text-button");
+ var restoreDefaults = this._appendSection().createChild("input", "settings-tab-text-button");
restoreDefaults.type = "button";
restoreDefaults.value = WebInspector.UIString("Restore defaults and reload");
restoreDefaults.addEventListener("click", restoreAndReload);
@@ -365,38 +218,118 @@
}
WebInspector.GenericSettingsTab.prototype = {
- _updateScriptDisabledCheckbox: function()
+ _populateSectionsFromExtensions: function()
{
+ /** @const */
+ var explicitSectionOrder = ["", "Appearance", "Elements", "Sources", "Profiler", "Console", "Extensions"];
+
+ var allExtensions = WebInspector.moduleManager.extensions("ui-setting");
+
+ /** @type {!StringMultimap.<!WebInspector.ModuleManager.Extension>} */
+ var extensionsBySectionId = new StringMultimap();
+ /** @type {!StringMultimap.<!WebInspector.ModuleManager.Extension>} */
+ var childSettingExtensionsByParentName = new StringMultimap();
+
+ allExtensions.forEach(function(extension) {
+ var descriptor = extension.descriptor();
+ var sectionName = descriptor["section"] || "";
+ if (!sectionName && descriptor["parentSettingName"]) {
+ childSettingExtensionsByParentName.put(descriptor["parentSettingName"], extension);
+ return;
+ }
+ extensionsBySectionId.put(sectionName, extension);
+ });
+
+ var sectionIds = extensionsBySectionId.keys();
+ var explicitlyOrderedSections = {};
+ for (var i = 0; i < explicitSectionOrder.length; ++i) {
+ explicitlyOrderedSections[explicitSectionOrder[i]] = true;
+ var extensions = /** @type {!Set.<!WebInspector.ModuleManager.Extension>} */ (extensionsBySectionId.get(explicitSectionOrder[i]));
+ if (!extensions)
+ continue;
+ this._addSectionWithExtensionProvidedSettings(explicitSectionOrder[i], extensions.items(), childSettingExtensionsByParentName);
+ }
+ for (var i = 0; i < sectionIds.length; ++i) {
+ if (explicitlyOrderedSections[sectionIds[i]])
+ continue;
+ this._addSectionWithExtensionProvidedSettings(sectionIds[i], /** @type {!Set.<!WebInspector.ModuleManager.Extension>} */ (extensionsBySectionId.get(sectionIds[i])).items(), childSettingExtensionsByParentName);
+ }
+ },
+
+ /**
+ * @param {string} sectionName
+ * @param {!Array.<!WebInspector.ModuleManager.Extension>} extensions
+ * @param {!StringMultimap.<!WebInspector.ModuleManager.Extension>} childSettingExtensionsByParentName
+ */
+ _addSectionWithExtensionProvidedSettings: function(sectionName, extensions, childSettingExtensionsByParentName)
+ {
+ var uiSectionName = sectionName && WebInspector.UIString(sectionName);
+ var sectionElement = this._appendSection(uiSectionName);
+ extensions.forEach(processSetting.bind(this, null));
+
/**
- * @param {?Protocol.Error} error
- * @param {string} status
+ * @param {?Element} parentFieldset
+ * @param {!WebInspector.ModuleManager.Extension} extension
* @this {WebInspector.GenericSettingsTab}
*/
- function executionStatusCallback(error, status)
+ function processSetting(parentFieldset, extension)
{
- if (error || !status)
+ var descriptor = extension.descriptor();
+ var experimentName = descriptor["experiment"];
+ if (experimentName && (!WebInspector.experimentsSettings[experimentName] || !WebInspector.experimentsSettings[experimentName].isEnabled()))
return;
- var forbidden = (status === "forbidden");
- var disabled = forbidden || (status === "disabled");
-
- this._disableJSInfo.classList.toggle("hidden", !forbidden);
- this._disableJSCheckbox.checked = disabled;
- this._disableJSCheckbox.disabled = forbidden;
+ var settingName = descriptor["settingName"];
+ var setting = WebInspector.settings[settingName];
+ var instance = extension.instance();
+ var settingControl;
+ if (instance && descriptor["settingType"] === "custom") {
+ settingControl = instance.settingElement();
+ if (!settingControl)
+ return;
+ }
+ if (!settingControl) {
+ var uiTitle = WebInspector.UIString(descriptor["title"]);
+ settingControl = createSettingControl.call(this, uiTitle, setting, descriptor, instance);
+ }
+ if (settingName) {
+ var childSettings = /** @type {!Set.<!WebInspector.ModuleManager.Extension>|undefined} */ (childSettingExtensionsByParentName.get(settingName));
+ if (childSettings) {
+ var fieldSet = WebInspector.SettingsUI.createSettingFieldset(setting);
+ settingControl.appendChild(fieldSet);
+ childSettings.items().forEach(function(item) { processSetting.call(this, fieldSet, item); }, this);
+ }
+ }
+ var containerElement = parentFieldset || sectionElement;
+ containerElement.appendChild(settingControl);
}
- PageAgent.getScriptExecutionStatus(executionStatusCallback.bind(this));
- },
-
- _javaScriptDisabledChanged: function()
- {
- // We need to manually update the checkbox state, since enabling JavaScript in the page can actually uncover the "forbidden" state.
- PageAgent.setScriptExecutionDisabled(WebInspector.settings.javaScriptDisabled.get(), this._updateScriptDisabledCheckbox.bind(this));
- },
-
- _skipStackFramesSwitchOrPatternChanged: function()
- {
- WebInspector.debuggerModel.applySkipStackFrameSettings();
+ /**
+ * @param {string} uiTitle
+ * @param {!WebInspector.Setting} setting
+ * @param {!Object} descriptor
+ * @param {?Object} instance
+ * @return {!Element}
+ * @this {WebInspector.GenericSettingsTab}
+ */
+ function createSettingControl(uiTitle, setting, descriptor, instance)
+ {
+ switch (descriptor["settingType"]) {
+ case "checkbox":
+ return WebInspector.SettingsUI.createSettingCheckbox(uiTitle, setting);
+ case "select":
+ var descriptorOptions = descriptor["options"]
+ var options = new Array(descriptorOptions.length);
+ for (var i = 0; i < options.length; ++i) {
+ // The third array item flags that the option name is "raw" (non-i18n-izable).
+ var optionName = descriptorOptions[i][2] ? descriptorOptions[i][0] : WebInspector.UIString(descriptorOptions[i][0]);
+ options[i] = [WebInspector.UIString(descriptorOptions[i][0]), descriptorOptions[i][1]];
+ }
+ return this._createSelectSetting(uiTitle, options, setting);
+ default:
+ throw "Invalid setting type: " + descriptor["settingType"];
+ }
+ }
},
/**
@@ -426,7 +359,7 @@
WebInspector.isolatedFileSystemManager.addEventListener(WebInspector.IsolatedFileSystemManager.Events.FileSystemRemoved, this._fileSystemRemoved, this);
this._commonSection = this._appendSection(WebInspector.UIString("Common"));
- var folderExcludePatternInput = this._createInputSetting(WebInspector.UIString("Folder exclude pattern"), WebInspector.settings.workspaceFolderExcludePattern, false, 0, "270px", WebInspector.SettingsScreen.regexValidator);
+ var folderExcludePatternInput = WebInspector.SettingsUI.createSettingInputField(WebInspector.UIString("Folder exclude pattern"), WebInspector.settings.workspaceFolderExcludePattern, false, 0, "270px", WebInspector.SettingsUI.regexValidator);
this._commonSection.appendChild(folderExcludePatternInput);
this._fileSystemsSection = this._appendSection(WebInspector.UIString("Folders"));
diff --git a/Source/devtools/front_end/SettingsUI.js b/Source/devtools/front_end/SettingsUI.js
index 25a4f6b..34a1a8f 100644
--- a/Source/devtools/front_end/SettingsUI.js
+++ b/Source/devtools/front_end/SettingsUI.js
@@ -80,6 +80,64 @@
}
/**
+ * @param {string} label
+ * @param {!WebInspector.Setting} setting
+ * @param {boolean} numeric
+ * @param {number=} maxLength
+ * @param {string=} width
+ * @param {function(string):?string=} validatorCallback
+ */
+WebInspector.SettingsUI.createSettingInputField = function(label, setting, numeric, maxLength, width, validatorCallback)
+{
+ var p = document.createElement("p");
+ var labelElement = p.createChild("label");
+ labelElement.textContent = label;
+ var inputElement = p.createChild("input");
+ inputElement.value = setting.get();
+ inputElement.type = "text";
+ if (numeric)
+ inputElement.className = "numeric";
+ if (maxLength)
+ inputElement.maxLength = maxLength;
+ if (width)
+ inputElement.style.width = width;
+
+ var errorMessageLabel;
+ if (validatorCallback) {
+ errorMessageLabel = p.createChild("div");
+ errorMessageLabel.classList.add("field-error-message");
+ inputElement.oninput = onInput;
+ onInput();
+ }
+
+ function onInput()
+ {
+ var error = validatorCallback(inputElement.value);
+ if (!error)
+ error = "";
+ errorMessageLabel.textContent = error;
+ }
+
+ function onBlur()
+ {
+ setting.set(numeric ? Number(inputElement.value) : inputElement.value);
+ }
+ inputElement.addEventListener("blur", onBlur, false);
+
+ return p;
+}
+
+WebInspector.SettingsUI.createCustomSetting = function(name, element)
+{
+ var p = document.createElement("p");
+ var fieldsetElement = document.createElement("fieldset");
+ fieldsetElement.createChild("label").textContent = name;
+ fieldsetElement.appendChild(element);
+ p.appendChild(fieldsetElement);
+ return p;
+}
+
+/**
* @param {!WebInspector.Setting} setting
* @return {!Element}
*/
@@ -95,3 +153,34 @@
fieldset.disabled = !setting.get();
}
}
+
+/**
+ * @param {string} text
+ * @return {?string}
+ */
+WebInspector.SettingsUI.regexValidator = function(text)
+{
+ var regex;
+ try {
+ regex = new RegExp(text);
+ } catch (e) {
+ }
+ return regex ? null : WebInspector.UIString("Invalid pattern");
+}
+
+/**
+ * @constructor
+ */
+WebInspector.UISettingDelegate = function()
+{
+}
+
+WebInspector.UISettingDelegate.prototype = {
+ /**
+ * @return {?Element}
+ */
+ settingElement: function()
+ {
+ return null;
+ }
+}
diff --git a/Source/devtools/front_end/SourcesPanel.js b/Source/devtools/front_end/SourcesPanel.js
index e617973..46a9673 100644
--- a/Source/devtools/front_end/SourcesPanel.js
+++ b/Source/devtools/front_end/SourcesPanel.js
@@ -237,7 +237,7 @@
if (details.reason === WebInspector.DebuggerModel.BreakReason.DOM) {
WebInspector.domBreakpointsSidebarPane.highlightBreakpoint(details.auxData);
- WebInspector.domBreakpointsSidebarPane.createBreakpointHitStatusMessage(details.auxData, didCreateBreakpointHitStatusMessage.bind(this));
+ WebInspector.domBreakpointsSidebarPane.createBreakpointHitStatusMessage(details, didCreateBreakpointHitStatusMessage.bind(this));
} else if (details.reason === WebInspector.DebuggerModel.BreakReason.EventListener) {
var eventName = details.auxData.eventName;
this.sidebarPanes.eventListenerBreakpoints.highlightBreakpoint(details.auxData.eventName);
@@ -583,6 +583,9 @@
delete this._skipExecutionLineRevealing;
},
+ /**
+ * @param {!WebInspector.DebuggerModel.Location} rawLocation
+ */
continueToLocation: function(rawLocation)
{
if (!this._paused)
@@ -591,7 +594,7 @@
delete this._skipExecutionLineRevealing;
this._paused = false;
this._clearInterface();
- WebInspector.debuggerModel.continueToLocation(rawLocation);
+ rawLocation.continueToLocation();
},
_toggleBreakpointsClicked: function(event)
@@ -996,7 +999,7 @@
vbox.element.appendChild(this._debugToolbarDrawer);
vbox.element.appendChild(this.debugToolbar);
vbox.element.appendChild(this.threadsToolbar.element);
- vbox.setMinimumSize(WebInspector.SourcesPanel.minToolbarWidth, 25);
+ vbox.setMinimumAndPreferredSizes(25, 25, WebInspector.SourcesPanel.minToolbarWidth, 100);
var sidebarPaneStack = new WebInspector.SidebarPaneStack();
sidebarPaneStack.element.classList.add("flex-auto");
sidebarPaneStack.show(vbox.element);
@@ -1239,3 +1242,87 @@
return true;
}
}
+
+/**
+ * @constructor
+ * @extends {WebInspector.UISettingDelegate}
+ */
+WebInspector.SourcesPanel.SkipStackFramePatternSettingDelegate = function()
+{
+ WebInspector.UISettingDelegate.call(this);
+}
+
+WebInspector.SourcesPanel.SkipStackFramePatternSettingDelegate.prototype = {
+ /**
+ * @override
+ * @return {!Element}
+ */
+ settingElement: function()
+ {
+ return WebInspector.SettingsUI.createSettingInputField(WebInspector.UIString("Pattern"), WebInspector.settings.skipStackFramesPattern, false, 1000, "100px", WebInspector.SettingsUI.regexValidator);
+ },
+
+ __proto__: WebInspector.UISettingDelegate.prototype
+}
+
+/**
+ * @constructor
+ * @extends {WebInspector.UISettingDelegate}
+ */
+WebInspector.SourcesPanel.DisableJavaScriptSettingDelegate = function()
+{
+ WebInspector.UISettingDelegate.call(this);
+}
+
+WebInspector.SourcesPanel.DisableJavaScriptSettingDelegate.prototype = {
+ /**
+ * @override
+ * @return {!Element}
+ */
+ settingElement: function()
+ {
+ var disableJSElement = WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("Disable JavaScript"), WebInspector.settings.javaScriptDisabled);
+ this._disableJSCheckbox = disableJSElement.getElementsByTagName("input")[0];
+ WebInspector.settings.javaScriptDisabled.addChangeListener(this._settingChanged, this);
+ var disableJSInfoParent = this._disableJSCheckbox.parentElement.createChild("span", "monospace");
+ this._disableJSInfo = disableJSInfoParent.createChild("span", "object-info-state-note hidden");
+ this._disableJSInfo.title = WebInspector.UIString("JavaScript is blocked on the inspected page (may be disabled in browser settings).");
+
+ WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.MainFrameNavigated, this._updateScriptDisabledCheckbox, this);
+ this._updateScriptDisabledCheckbox();
+ return disableJSElement;
+ },
+
+ /**
+ * @param {!WebInspector.Event} event
+ */
+ _settingChanged: function(event)
+ {
+ PageAgent.setScriptExecutionDisabled(event.data, this._updateScriptDisabledCheckbox.bind(this));
+ },
+
+ _updateScriptDisabledCheckbox: function()
+ {
+ PageAgent.getScriptExecutionStatus(executionStatusCallback.bind(this));
+
+ /**
+ * @param {?Protocol.Error} error
+ * @param {string} status
+ * @this {WebInspector.SourcesPanel.DisableJavaScriptSettingDelegate}
+ */
+ function executionStatusCallback(error, status)
+ {
+ if (error || !status)
+ return;
+
+ var forbidden = (status === "forbidden");
+ var disabled = forbidden || (status === "disabled");
+
+ this._disableJSInfo.classList.toggle("hidden", !forbidden);
+ this._disableJSCheckbox.checked = disabled;
+ this._disableJSCheckbox.disabled = forbidden;
+ }
+ },
+
+ __proto__: WebInspector.UISettingDelegate.prototype
+}
diff --git a/Source/devtools/front_end/SourcesSearchScope.js b/Source/devtools/front_end/SourcesSearchScope.js
index 60f7311..f623641 100644
--- a/Source/devtools/front_end/SourcesSearchScope.js
+++ b/Source/devtools/front_end/SourcesSearchScope.js
@@ -115,6 +115,8 @@
return;
}
+ addDirtyFiles();
+
if (!files.length) {
progress.done();
callback();
@@ -130,6 +132,19 @@
for (var i = 0; i < maxFileContentRequests && i < files.length; ++i)
scheduleSearchInNextFileOrFinish.call(this);
+ function addDirtyFiles()
+ {
+ var matchingFiles = StringSet.fromArray(files);
+ var uiSourceCodes = project.uiSourceCodes();
+ for (var i = 0; i < uiSourceCodes.length; ++i) {
+ if (!uiSourceCodes[i].isDirty())
+ continue;
+ var path = uiSourceCodes[i].path();
+ if (!matchingFiles.contains(path))
+ files.push(path);
+ }
+ }
+
/**
* @param {!string} path
* @this {WebInspector.SourcesSearchScope}
@@ -143,7 +158,19 @@
scheduleSearchInNextFileOrFinish.call(this);
return;
}
- uiSourceCode.requestContent(contentLoaded.bind(this, path));
+ if (uiSourceCode.isDirty())
+ contentLoaded.call(this, uiSourceCode.path(), uiSourceCode.workingCopy());
+ else
+ uiSourceCode.checkContentUpdated(contentUpdated.bind(this, uiSourceCode));
+ }
+
+ /**
+ * @param {!WebInspector.UISourceCode} uiSourceCode
+ * @this {WebInspector.SourcesSearchScope}
+ */
+ function contentUpdated(uiSourceCode)
+ {
+ uiSourceCode.requestContent(contentLoaded.bind(this, uiSourceCode.path()));
}
/**
diff --git a/Source/devtools/front_end/SourcesView.js b/Source/devtools/front_end/SourcesView.js
index da99a3a..67f3f3c 100644
--- a/Source/devtools/front_end/SourcesView.js
+++ b/Source/devtools/front_end/SourcesView.js
@@ -16,7 +16,7 @@
WebInspector.VBox.call(this);
this.registerRequiredCSS("sourcesView.css");
this.element.id = "sources-panel-sources-view";
- this.setMinimumSize(50, 25);
+ this.setMinimumAndPreferredSizes(50, 25, 150, 100);
this._workspace = workspace;
this._sourcesPanel = sourcesPanel;
diff --git a/Source/devtools/front_end/SplitView.js b/Source/devtools/front_end/SplitView.js
index 7e55d92..5eb9d67 100644
--- a/Source/devtools/front_end/SplitView.js
+++ b/Source/devtools/front_end/SplitView.js
@@ -134,7 +134,7 @@
this._restoreAndApplyShowModeFromSettings();
this._updateShowHideSidebarButton();
this._updateResizersClass();
- this.invalidateMinimumSize();
+ this.invalidateConstraints();
},
/**
@@ -349,7 +349,7 @@
{
this._savedSidebarSize = size;
this._saveSetting();
- this._innerSetSidebarSize(size, false);
+ this._innerSetSidebarSize(size, false, true);
},
/**
@@ -380,19 +380,20 @@
this._saveShowModeToSettings();
this._updateShowHideSidebarButton();
this.dispatchEventToListeners(WebInspector.SplitView.Events.ShowModeChanged, showMode);
- this.invalidateMinimumSize();
+ this.invalidateConstraints();
},
/**
* @param {number} size
* @param {boolean} animate
+ * @param {boolean=} userAction
*/
- _innerSetSidebarSize: function(size, animate)
+ _innerSetSidebarSize: function(size, animate, userAction)
{
if (this._showMode !== WebInspector.SplitView.ShowMode.Both || !this.isShowing())
return;
- size = this._applyConstraints(size);
+ size = this._applyConstraints(size, userAction);
if (this._sidebarSize === size)
return;
@@ -517,28 +518,54 @@
/**
* @param {number} sidebarSize
+ * @param {boolean=} userAction
* @return {number}
*/
- _applyConstraints: function(sidebarSize)
+ _applyConstraints: function(sidebarSize, userAction)
{
var totalSize = this.totalSize();
- var size = this._sidebarView.minimumSize();
- var from = this.isVertical() ? size.width : size.height;
- if (!from)
- from = WebInspector.SplitView.MinPadding;
+ var constraints = this._sidebarView.constraints();
+ var minSidebarSize = this.isVertical() ? constraints.minimum.width : constraints.minimum.height;
+ if (!minSidebarSize)
+ minSidebarSize = WebInspector.SplitView.MinPadding;
- size = this._mainView.minimumSize();
- var minMainSize = this.isVertical() ? size.width : size.height;
+ var preferredSidebarSize = this.isVertical() ? constraints.preferred.width : constraints.preferred.height;
+ if (!preferredSidebarSize)
+ preferredSidebarSize = WebInspector.SplitView.MinPadding;
+ // Allow sidebar to be less than preferred by explicit user action.
+ if (sidebarSize < preferredSidebarSize)
+ preferredSidebarSize = Math.max(sidebarSize, minSidebarSize);
+
+ constraints = this._mainView.constraints();
+ var minMainSize = this.isVertical() ? constraints.minimum.width : constraints.minimum.height;
if (!minMainSize)
minMainSize = WebInspector.SplitView.MinPadding;
- var to = totalSize - minMainSize;
- if (from <= to)
- return Number.constrain(sidebarSize, from, to);
+ var preferredMainSize = this.isVertical() ? constraints.preferred.width : constraints.preferred.height;
+ if (!preferredMainSize)
+ preferredMainSize = WebInspector.SplitView.MinPadding;
+ var savedMainSize = this.isVertical() ? this._savedVerticalMainSize : this._savedHorizontalMainSize;
+ if (typeof savedMainSize !== "undefined")
+ preferredMainSize = Math.min(preferredMainSize, savedMainSize);
+ if (userAction)
+ preferredMainSize = minMainSize;
- // If we don't have enough space (which is a very rare case), prioritize main view.
- return Math.max(0, to);
+ // Enough space for preferred.
+ var totalPreferred = preferredMainSize + preferredSidebarSize;
+ if (totalPreferred <= totalSize)
+ return Number.constrain(sidebarSize, preferredSidebarSize, totalSize - preferredMainSize);
+
+ // Enough space for minimum.
+ if (minMainSize + minSidebarSize <= totalSize) {
+ var delta = totalPreferred - totalSize;
+ var sidebarDelta = delta * preferredSidebarSize / totalPreferred;
+ sidebarSize = preferredSidebarSize - sidebarDelta;
+ return Number.constrain(sidebarSize, minSidebarSize, totalSize - minMainSize);
+ }
+
+ // Not enough space even for minimum sizes.
+ return Math.max(0, totalSize - minMainSize);
},
wasShown: function()
@@ -563,22 +590,27 @@
},
/**
- * @return {!Size}
+ * @return {!Constraints}
*/
- calculateMinimumSize: function()
+ calculateConstraints: function()
{
if (this._showMode === WebInspector.SplitView.ShowMode.OnlyMain)
- return this._mainView.minimumSize();
+ return this._mainView.constraints();
if (this._showMode === WebInspector.SplitView.ShowMode.OnlySidebar)
- return this._sidebarView.minimumSize();
+ return this._sidebarView.constraints();
- var mainSize = this._mainView.minimumSize();
- var sidebarSize = this._sidebarView.minimumSize();
+ var mainConstraints = this._mainView.constraints();
+ var sidebarConstraints = this._sidebarView.constraints();
var min = WebInspector.SplitView.MinPadding;
- if (this._isVertical)
- return new Size((mainSize.width || min) + (sidebarSize.width || min), Math.max(mainSize.height, sidebarSize.height));
- else
- return new Size(Math.max(mainSize.width, sidebarSize.width), (mainSize.height || min) + (sidebarSize.height || min));
+ if (this._isVertical) {
+ mainConstraints = mainConstraints.widthToMax(min);
+ sidebarConstraints = sidebarConstraints.widthToMax(min);
+ return mainConstraints.addWidth(sidebarConstraints).heightToMax(sidebarConstraints);
+ } else {
+ mainConstraints = mainConstraints.heightToMax(min);
+ sidebarConstraints = sidebarConstraints.heightToMax(min);
+ return mainConstraints.widthToMax(sidebarConstraints).addHeight(sidebarConstraints);
+ }
},
/**
@@ -603,10 +635,14 @@
var dipEventPosition = (this._isVertical ? event.pageX : event.pageY) * WebInspector.zoomManager.zoomFactor();
var newOffset = dipEventPosition + this._dragOffset;
var newSize = (this._secondIsSidebar ? this.totalSize() - newOffset : newOffset);
- var constrainedSize = this._applyConstraints(newSize);
+ var constrainedSize = this._applyConstraints(newSize, true);
this._savedSidebarSize = constrainedSize;
this._saveSetting();
- this._innerSetSidebarSize(constrainedSize, false);
+ this._innerSetSidebarSize(constrainedSize, false, true);
+ if (this.isVertical())
+ this._savedVerticalMainSize = this.totalSize() - this._sidebarSize;
+ else
+ this._savedHorizontalMainSize = this.totalSize() - this._sidebarSize;
event.preventDefault();
},
diff --git a/Source/devtools/front_end/StylesSidebarPane.js b/Source/devtools/front_end/StylesSidebarPane.js
index d98c1cf..0a92f07 100644
--- a/Source/devtools/front_end/StylesSidebarPane.js
+++ b/Source/devtools/front_end/StylesSidebarPane.js
@@ -31,7 +31,7 @@
* @constructor
* @extends {WebInspector.SidebarPane}
* @param {!WebInspector.ComputedStyleSidebarPane} computedStylePane
- * @param {function(!DOMAgent.NodeId, string, boolean)} setPseudoClassCallback
+ * @param {function(!WebInspector.DOMNode, string, boolean)=} setPseudoClassCallback
*/
WebInspector.StylesSidebarPane = function(computedStylePane, setPseudoClassCallback)
{
@@ -55,6 +55,7 @@
this._setPseudoClassCallback = setPseudoClassCallback;
this.element.addEventListener("contextmenu", this._contextMenuEventFired.bind(this), true);
WebInspector.settings.colorFormat.addChangeListener(this._colorFormatSettingChanged.bind(this));
+ WebInspector.settings.showUserAgentStyles.addChangeListener(this._showUserAgentStylesSettingChanged.bind(this));
this._createElementStatePane();
this.bodyElement.appendChild(this._elementStatePane);
@@ -64,14 +65,6 @@
this._spectrumHelper = new WebInspector.SpectrumPopupHelper();
this._linkifier = new WebInspector.Linkifier(new WebInspector.Linkifier.DefaultCSSFormatter());
- WebInspector.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetAdded, this._styleSheetOrMediaQueryResultChanged, this);
- WebInspector.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetRemoved, this._styleSheetOrMediaQueryResultChanged, this);
- WebInspector.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetChanged, this._styleSheetOrMediaQueryResultChanged, this);
- WebInspector.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.MediaQueryResultChanged, this._styleSheetOrMediaQueryResultChanged, this);
- WebInspector.domModel.addEventListener(WebInspector.DOMModel.Events.AttrModified, this._attributeChanged, this);
- WebInspector.domModel.addEventListener(WebInspector.DOMModel.Events.AttrRemoved, this._attributeChanged, this);
- WebInspector.settings.showUserAgentStyles.addChangeListener(this._showUserAgentStylesSettingChanged.bind(this));
- WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameResized, this._frameResized, this);
this.element.classList.add("styles-pane");
this.element.classList.toggle("show-user-styles", WebInspector.settings.showUserAgentStyles.get());
this.element.addEventListener("mousemove", this._mouseMovedOverElement.bind(this), false);
@@ -169,6 +162,18 @@
WebInspector.StylesSidebarPane.prototype = {
/**
+ * @param {!WebInspector.CSSRule} editedRule
+ * @param {!WebInspector.TextRange} oldRange
+ * @param {!WebInspector.TextRange} newRange
+ */
+ _styleSheetRuleEdited: function(editedRule, oldRange, newRange)
+ {
+ var styleRuleSections = this.sections[0];
+ for (var i = 1; i < styleRuleSections.length; ++i)
+ styleRuleSections[i]._styleSheetRuleEdited(editedRule, oldRange, newRange);
+ },
+
+ /**
* @param {?Event} event
*/
_contextMenuEventFired: function(event)
@@ -211,15 +216,15 @@
get _forcedPseudoClasses()
{
- return this.node ? (this.node.getUserProperty("pseudoState") || undefined) : undefined;
+ return this._node ? (this._node.getUserProperty(WebInspector.CSSStyleModel.PseudoStatePropertyName) || undefined) : undefined;
},
_updateForcedPseudoStateInputs: function()
{
- if (!this.node)
+ if (!this._node)
return;
- var hasPseudoType = !!this.node.pseudoType();
+ var hasPseudoType = !!this._node.pseudoType();
this._elementStateButton.classList.toggle("hidden", hasPseudoType);
this._elementStatePane.classList.toggle("expanded", !hasPseudoType && this._elementStateButton.classList.contains("toggled"));
@@ -244,9 +249,9 @@
var refresh = false;
if (forceUpdate)
- delete this.node;
+ delete this._node;
- if (!forceUpdate && (node === this.node))
+ if (!forceUpdate && (node === this._node))
refresh = true;
if (node && node.nodeType() === Node.TEXT_NODE && node.parentNode)
@@ -255,10 +260,11 @@
if (node && node.nodeType() !== Node.ELEMENT_NODE)
node = null;
- if (node)
- this.node = node;
- else
- node = this.node;
+ if (node) {
+ this._updateTarget(node.target());
+ this._node = node;
+ } else
+ node = this._node;
this._updateForcedPseudoStateInputs();
@@ -269,6 +275,32 @@
},
/**
+ * @param {!WebInspector.Target} target
+ */
+ _updateTarget: function(target)
+ {
+ if (this._target === target)
+ return;
+ if (this._target) {
+ this._target.cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.StyleSheetAdded, this._styleSheetOrMediaQueryResultChanged, this);
+ this._target.cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.StyleSheetRemoved, this._styleSheetOrMediaQueryResultChanged, this);
+ this._target.cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.StyleSheetChanged, this._styleSheetOrMediaQueryResultChanged, this);
+ this._target.cssModel.removeEventListener(WebInspector.CSSStyleModel.Events.MediaQueryResultChanged, this._styleSheetOrMediaQueryResultChanged, this);
+ this._target.domModel.removeEventListener(WebInspector.DOMModel.Events.AttrModified, this._attributeChanged, this);
+ this._target.domModel.removeEventListener(WebInspector.DOMModel.Events.AttrRemoved, this._attributeChanged, this);
+ this._target.resourceTreeModel.removeEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameResized, this._frameResized, this);
+ }
+ this._target = target;
+ this._target.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetAdded, this._styleSheetOrMediaQueryResultChanged, this);
+ this._target.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetRemoved, this._styleSheetOrMediaQueryResultChanged, this);
+ this._target.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetChanged, this._styleSheetOrMediaQueryResultChanged, this);
+ this._target.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.MediaQueryResultChanged, this._styleSheetOrMediaQueryResultChanged, this);
+ this._target.domModel.addEventListener(WebInspector.DOMModel.Events.AttrModified, this._attributeChanged, this);
+ this._target.domModel.addEventListener(WebInspector.DOMModel.Events.AttrRemoved, this._attributeChanged, this);
+ this._target.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameResized, this._frameResized, this);
+ },
+
+ /**
* @param {!WebInspector.StylePropertiesSection=} editedSection
* @param {boolean=} forceFetchComputedStyle
* @param {function()=} userCallback
@@ -284,7 +316,7 @@
}.bind(this);
if (this._refreshUpdateInProgress) {
- this._lastNodeForInnerRefresh = this.node;
+ this._lastNodeForInnerRefresh = this._node;
return;
}
@@ -306,7 +338,7 @@
return;
}
- if (this.node === node && computedStyle)
+ if (this._node === node && computedStyle)
this._innerRefreshUpdate(node, computedStyle, editedSection);
callbackWrapper();
@@ -314,7 +346,7 @@
if (this._computedStylePane.isShowing() || forceFetchComputedStyle) {
this._refreshUpdateInProgress = true;
- WebInspector.cssModel.getComputedStyleAsync(node.id, computedStyleCallback.bind(this));
+ this._target.cssModel.getComputedStyleAsync(node.id, computedStyleCallback.bind(this));
} else {
this._innerRefreshUpdate(node, null, editedSection);
callbackWrapper();
@@ -324,7 +356,7 @@
_rebuildUpdate: function()
{
if (this._rebuildUpdateInProgress) {
- this._lastNodeForInnerRebuild = this.node;
+ this._lastNodeForInnerRebuild = this._node;
return;
}
@@ -347,13 +379,13 @@
var lastNodeForRebuild = this._lastNodeForInnerRebuild;
if (lastNodeForRebuild) {
delete this._lastNodeForInnerRebuild;
- if (lastNodeForRebuild !== this.node) {
+ if (lastNodeForRebuild !== this._node) {
this._rebuildUpdate();
return;
}
}
- if (matchedResult && this.node === node) {
+ if (matchedResult && this._node === node) {
resultStyles.matchedCSSRules = matchedResult.matchedCSSRules;
resultStyles.pseudoElements = matchedResult.pseudoElements;
resultStyles.inherited = matchedResult.inherited;
@@ -386,9 +418,9 @@
}
if (this._computedStylePane.isShowing())
- WebInspector.cssModel.getComputedStyleAsync(node.id, computedCallback);
- WebInspector.cssModel.getInlineStylesAsync(node.id, inlineCallback);
- WebInspector.cssModel.getMatchedStylesAsync(node.id, true, true, stylesCallback.bind(this));
+ this._target.cssModel.getComputedStyleAsync(node.id, computedCallback);
+ this._target.cssModel.getInlineStylesAsync(node.id, inlineCallback);
+ this._target.cssModel.getMatchedStylesAsync(node.id, true, true, stylesCallback.bind(this));
},
/**
@@ -396,7 +428,7 @@
*/
_validateNode: function(userCallback)
{
- if (!this.node) {
+ if (!this._node) {
this._sectionsContainer.removeChildren();
this._computedStylePane.bodyElement.removeChildren();
this.sections = {};
@@ -404,7 +436,7 @@
userCallback();
return null;
}
- return this.node;
+ return this._node;
},
_styleSheetOrMediaQueryResultChanged: function()
@@ -447,7 +479,7 @@
_canAffectCurrentStyles: function(node)
{
- return this.node && (this.node === node || node.parentNode === this.node.parentNode || node.isAncestor(this.node));
+ return this._node && (this._node === node || node.parentNode === this._node.parentNode || node.isAncestor(this._node));
},
_innerRefreshUpdate: function(node, computedStyle, editedSection)
@@ -769,7 +801,7 @@
*/
addBlankSection: function()
{
- var blankSection = new WebInspector.BlankStylePropertiesSection(this, this.node ? WebInspector.DOMPresentationUtils.simpleSelector(this.node) : "");
+ var blankSection = new WebInspector.BlankStylePropertiesSection(this, this._node ? WebInspector.DOMPresentationUtils.simpleSelector(this._node) : "");
var elementStyleSection = this.sections[0][1];
this._sectionsContainer.insertBefore(blankSection.element, elementStyleSection.element.nextSibling);
@@ -820,7 +852,7 @@
var node = this._validateNode();
if (!node)
return;
- this._setPseudoClassCallback(node.id, event.target.state, event.target.checked);
+ this._setPseudoClassCallback(node, event.target.state, event.target.checked);
}
/**
@@ -1060,55 +1092,6 @@
// We don't really use properties' disclosure.
this.propertiesElement.classList.remove("properties-tree");
- if (styleRule.media) {
- for (var i = styleRule.media.length - 1; i >= 0; --i) {
- var media = styleRule.media[i];
- var mediaDataElement = this.titleElement.createChild("div", "media");
- var mediaText;
- switch (media.source) {
- case WebInspector.CSSMedia.Source.LINKED_SHEET:
- case WebInspector.CSSMedia.Source.INLINE_SHEET:
- mediaText = "media=\"" + media.text + "\"";
- break;
- case WebInspector.CSSMedia.Source.MEDIA_RULE:
- mediaText = "@media " + media.text;
- break;
- case WebInspector.CSSMedia.Source.IMPORT_RULE:
- mediaText = "@import " + media.text;
- break;
- }
-
- if (media.sourceURL) {
- var refElement = mediaDataElement.createChild("div", "subtitle");
- var rawLocation;
- var mediaHeader;
- if (media.range) {
- mediaHeader = media.header();
- if (mediaHeader) {
- var lineNumber = media.lineNumberInSource();
- var columnNumber = media.columnNumberInSource();
- console.assert(typeof lineNumber !== "undefined" && typeof columnNumber !== "undefined");
- rawLocation = new WebInspector.CSSLocation(media.sourceURL, lineNumber, columnNumber);
- }
- }
-
- var anchor;
- if (rawLocation)
- anchor = this._parentPane._linkifier.linkifyCSSLocation(mediaHeader.id, rawLocation);
- else {
- // The "linkedStylesheet" case.
- anchor = WebInspector.linkifyResourceAsNode(media.sourceURL, undefined, "subtitle", media.sourceURL);
- }
- anchor.style.float = "right";
- refElement.appendChild(anchor);
- }
-
- var mediaTextElement = mediaDataElement.createChild("span");
- mediaTextElement.textContent = mediaText;
- mediaTextElement.title = media.text;
- }
- }
-
var selectorContainer = document.createElement("div");
this._selectorElement = document.createElement("span");
this._selectorElement.textContent = styleRule.selectorText;
@@ -1144,6 +1127,8 @@
this._selectorRefElement = document.createElement("div");
this._selectorRefElement.className = "subtitle";
+ this._mediaListElement = this.titleElement.createChild("div", "media-list");
+ this._updateMediaList();
this._updateRuleOrigin();
selectorContainer.insertBefore(this._selectorRefElement, selectorContainer.firstChild);
this.titleElement.appendChild(selectorContainer);
@@ -1160,9 +1145,80 @@
}
WebInspector.StylePropertiesSection.prototype = {
- get pane()
+ /**
+ * @param {!WebInspector.CSSRule} editedRule
+ * @param {!WebInspector.TextRange} oldRange
+ * @param {!WebInspector.TextRange} newRange
+ */
+ _styleSheetRuleEdited: function(editedRule, oldRange, newRange)
{
- return this._parentPane;
+ if (!this.rule || !this.rule.id)
+ return;
+ if (this.rule !== editedRule)
+ this.rule.sourceStyleSheetEdited(this.rule.id.styleSheetId, oldRange, newRange);
+ this._updateMediaList();
+ this._updateRuleOrigin();
+ },
+
+ /**
+ * @param {!Object} styleRule
+ */
+ _createMediaList: function(styleRule)
+ {
+ if (!styleRule.media)
+ return;
+ for (var i = styleRule.media.length - 1; i >= 0; --i) {
+ var media = styleRule.media[i];
+ var mediaDataElement = this._mediaListElement.createChild("div", "media");
+ var mediaText;
+ switch (media.source) {
+ case WebInspector.CSSMedia.Source.LINKED_SHEET:
+ case WebInspector.CSSMedia.Source.INLINE_SHEET:
+ mediaText = "media=\"" + media.text + "\"";
+ break;
+ case WebInspector.CSSMedia.Source.MEDIA_RULE:
+ mediaText = "@media " + media.text;
+ break;
+ case WebInspector.CSSMedia.Source.IMPORT_RULE:
+ mediaText = "@import " + media.text;
+ break;
+ }
+
+ if (media.sourceURL) {
+ var refElement = mediaDataElement.createChild("div", "subtitle");
+ var rawLocation;
+ var mediaHeader;
+ if (media.range) {
+ mediaHeader = media.header();
+ if (mediaHeader) {
+ var lineNumber = media.lineNumberInSource();
+ var columnNumber = media.columnNumberInSource();
+ console.assert(typeof lineNumber !== "undefined" && typeof columnNumber !== "undefined");
+ rawLocation = new WebInspector.CSSLocation(this._parentPane._target, media.sourceURL, lineNumber, columnNumber);
+ }
+ }
+
+ var anchor;
+ if (rawLocation)
+ anchor = this._parentPane._linkifier.linkifyCSSLocation(mediaHeader.id, rawLocation);
+ else {
+ // The "linkedStylesheet" case.
+ anchor = WebInspector.linkifyResourceAsNode(media.sourceURL, undefined, "subtitle", media.sourceURL);
+ }
+ anchor.style.float = "right";
+ refElement.appendChild(anchor);
+ }
+
+ var mediaTextElement = mediaDataElement.createChild("span");
+ mediaTextElement.textContent = mediaText;
+ mediaTextElement.title = media.text;
+ }
+ },
+
+ _updateMediaList: function()
+ {
+ this._mediaListElement.removeChildren();
+ this._createMediaList(this.styleRule);
},
collapse: function()
@@ -1391,7 +1447,7 @@
var isSelectorMatching = matchingSelectors[currentMatch] === i;
if (isSelectorMatching)
++currentMatch;
- var rawLocation = new WebInspector.CSSLocation(rule.sourceURL, rule.lineNumberInSource(i), rule.columnNumberInSource(i));
+ var rawLocation = new WebInspector.CSSLocation(this._parentPane._target, rule.sourceURL, rule.lineNumberInSource(i), rule.columnNumberInSource(i));
var matchingSelectorClass = isSelectorMatching ? " selector-matches" : "";
var selectorElement = document.createElement("span");
selectorElement.className = "simple-selector" + matchingSelectorClass;
@@ -1410,7 +1466,7 @@
_markSelectorHighlights: function()
{
var selectors = this._selectorElement.getElementsByClassName("simple-selector");
- var regex = this.pane.filterRegex();
+ var regex = this._parentPane.filterRegex();
for (var i = 0; i < selectors.length; ++i) {
var selectorMatchesFilter = regex && regex.test(selectors[i].textContent);
selectors[i].classList.toggle("filter-match", selectorMatchesFilter);
@@ -1466,7 +1522,7 @@
if (this.styleRule.sourceURL) {
var firstMatchingIndex = this.styleRule.rule.matchingSelectors && this.rule.matchingSelectors.length ? this.rule.matchingSelectors[0] : 0;
- var matchingSelectorLocation = new WebInspector.CSSLocation(this.styleRule.sourceURL, this.rule.lineNumberInSource(firstMatchingIndex), this.rule.columnNumberInSource(firstMatchingIndex));
+ var matchingSelectorLocation = new WebInspector.CSSLocation(this._parentPane._target, this.styleRule.sourceURL, this.rule.lineNumberInSource(firstMatchingIndex), this.rule.columnNumberInSource(firstMatchingIndex));
return this._parentPane._linkifier.linkifyCSSLocation(this.rule.id.styleSheetId, matchingSelectorLocation) || linkifyUncopyable(this.styleRule.sourceURL, this.rule.lineNumberInSource());
}
@@ -1510,7 +1566,7 @@
{
if (WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event) && this.navigable && event.target.classList.contains("simple-selector")) {
var index = event.target._selectorIndex;
- var styleSheetHeader = WebInspector.cssModel.styleSheetHeaderForId(this.rule.id.styleSheetId);
+ var styleSheetHeader = this._parentPane._target.cssModel.styleSheetHeaderForId(this.rule.id.styleSheetId);
var uiLocation = styleSheetHeader.rawLocationToUILocation(this.rule.lineNumberInSource(index), this.rule.columnNumberInSource(index));
WebInspector.Revealer.reveal(uiLocation);
return;
@@ -1590,7 +1646,7 @@
return;
}
- var selectedNode = this._parentPane.node;
+ var selectedNode = this._parentPane._node;
/**
* @param {!WebInspector.CSSRule} newRule
@@ -1607,11 +1663,12 @@
this.element.classList.remove("no-affect");
}
+ var oldSelectorRange = this.rule.selectorRange;
this.rule = newRule;
this.styleRule = { section: this, style: newRule.style, selectorText: newRule.selectorText, media: newRule.media, sourceURL: newRule.resourceURL(), rule: newRule };
this._parentPane.update(selectedNode);
- this._updateRuleOrigin();
+ this._parentPane._styleSheetRuleEdited(this.rule, oldSelectorRange, this.rule.selectorRange);
finishOperationAndMoveEditor.call(this, moveDirection);
}
@@ -1627,7 +1684,7 @@
// This gets deleted in finishOperationAndMoveEditor(), which is called both on success and failure.
this._parentPane._userOperation = true;
- WebInspector.cssModel.setRuleSelector(this.rule.id, selectedNode ? selectedNode.id : 0, newContent, successCallback.bind(this), finishOperationAndMoveEditor.bind(this, moveDirection));
+ this._parentPane._target.cssModel.setRuleSelector(this.rule.id, selectedNode ? selectedNode.id : 0, newContent, successCallback.bind(this), finishOperationAndMoveEditor.bind(this, moveDirection));
},
_updateRuleOrigin: function()
@@ -1866,7 +1923,8 @@
newContent = newContent.trim();
this._parentPane._userOperation = true;
- WebInspector.cssModel.requestViaInspectorStylesheet(this.pane.node, viaInspectorCallback.bind(this));
+ var cssModel = this._parentPane._target.cssModel;
+ cssModel.requestViaInspectorStylesheet(this._parentPane._node, viaInspectorCallback.bind(this));
/**
* @this {WebInspector.BlankStylePropertiesSection}
@@ -1878,7 +1936,7 @@
this.editingSelectorCancelled();
return;
}
- WebInspector.cssModel.addRule(styleSheetHeader.id, this.pane.node, newContent, successCallback.bind(this), this.editingSelectorCancelled.bind(this));
+ cssModel.addRule(styleSheetHeader.id, this._parentPane._node, newContent, successCallback.bind(this), this.editingSelectorCancelled.bind(this));
}
},
@@ -1891,7 +1949,7 @@
}
this._editingSelectorEnded();
- this.pane.removeSection(this);
+ this._parentPane.removeSection(this);
},
makeNormal: function(styleRule)
@@ -2365,7 +2423,7 @@
*/
node: function()
{
- return this._stylesPane.node;
+ return this._stylesPane._node;
},
/**
@@ -2417,7 +2475,7 @@
*/
node: function()
{
- return this._parentPane.node;
+ return this._parentPane._node;
},
/**
@@ -2450,8 +2508,8 @@
_updatePane: function(userCallback)
{
var section = this.section();
- if (section && section.pane)
- section.pane._refreshUpdate(section, false, userCallback);
+ if (section && section._parentPane)
+ section._parentPane._refreshUpdate(section, false, userCallback);
else {
if (userCallback)
userCallback();
@@ -2459,6 +2517,22 @@
},
/**
+ * @param {!WebInspector.CSSStyleDeclaration} newStyle
+ */
+ _applyNewStyle: function(newStyle)
+ {
+ newStyle.parentRule = this.style.parentRule;
+ var oldStyleRange = /** @type {!WebInspector.TextRange} */ (this.style.range);
+ var newStyleRange = /** @type {!WebInspector.TextRange} */ (newStyle.range);
+ this.style = newStyle;
+ this._styleRule.style = newStyle;
+ if (this.style.parentRule) {
+ this.style.parentRule.style = this.style;
+ this._parentPane._styleSheetRuleEdited(this.style.parentRule, oldStyleRange, newStyleRange);
+ }
+ },
+
+ /**
* @param {?Event} event
*/
toggleEnabled: function(event)
@@ -2475,14 +2549,11 @@
if (!newStyle)
return;
-
- newStyle.parentRule = this.style.parentRule;
- this.style = newStyle;
- this._styleRule.style = newStyle;
+ this._applyNewStyle(newStyle);
var section = this.section();
- if (section && section.pane)
- section.pane.dispatchEventToListeners("style property toggled");
+ if (section && section._parentPane)
+ section._parentPane.dispatchEventToListeners("style property toggled");
this._updatePane();
}
@@ -3053,7 +3124,7 @@
return;
}
- var currentNode = this._parentPane.node;
+ var currentNode = this._parentPane._node;
if (updateInterface)
this._parentPane._userOperation = true;
@@ -3073,16 +3144,14 @@
userCallback();
return;
}
+ this._applyNewStyle(newStyle);
if (this._newProperty)
this._newPropertyInStyle = true;
- newStyle.parentRule = this.style.parentRule;
- this.style = newStyle;
- this.property = newStyle.propertyAt(this.property.index);
- this._styleRule.style = this.style;
- if (section && section.pane)
- section.pane.dispatchEventToListeners("style edited");
+ this.property = newStyle.propertyAt(this.property.index);
+ if (section && section._parentPane)
+ section._parentPane.dispatchEventToListeners("style edited");
if (updateInterface && currentNode === this.node()) {
this._updatePane(userCallback);
diff --git a/Source/devtools/front_end/StylesSourceMapping.js b/Source/devtools/front_end/StylesSourceMapping.js
index 1be84aa..788cf5a 100644
--- a/Source/devtools/front_end/StylesSourceMapping.js
+++ b/Source/devtools/front_end/StylesSourceMapping.js
@@ -72,7 +72,7 @@
*/
uiLocationToRawLocation: function(uiSourceCode, lineNumber, columnNumber)
{
- return new WebInspector.CSSLocation(uiSourceCode.url || "", lineNumber, columnNumber);
+ return new WebInspector.CSSLocation(this._cssModel.target(), uiSourceCode.url || "", lineNumber, columnNumber);
},
/**
diff --git a/Source/devtools/front_end/TabbedPane.js b/Source/devtools/front_end/TabbedPane.js
index 3a93baf..5d344f1 100644
--- a/Source/devtools/front_end/TabbedPane.js
+++ b/Source/devtools/front_end/TabbedPane.js
@@ -83,7 +83,7 @@
set verticalTabLayout(verticalTabLayout)
{
this._verticalTabLayout = verticalTabLayout;
- this.invalidateMinimumSize();
+ this.invalidateConstraints();
},
/**
@@ -376,20 +376,22 @@
var effectiveTab = this._currentTab || this._tabsHistory[0];
if (effectiveTab)
this.selectTab(effectiveTab.id);
- this.invalidateMinimumSize();
+ this.invalidateConstraints();
},
/**
- * @return {!Size}
+ * @return {!Constraints}
*/
- calculateMinimumSize: function()
+ calculateConstraints: function()
{
- var size = WebInspector.VBox.prototype.calculateMinimumSize.call(this);
+ var constraints = WebInspector.VBox.prototype.calculateConstraints.call(this);
+ var minContentConstraints = new Constraints(new Size(0, 0), new Size(50, 50));
+ constraints = constraints.widthToMax(minContentConstraints).heightToMax(minContentConstraints);
if (this._verticalTabLayout)
- size.width += this._headerElement.offsetWidth;
+ constraints = constraints.addWidth(new Constraints(new Size(this._headerElement.offsetWidth, 0)));
else
- size.height += this._headerElement.offsetHeight;
- return size;
+ constraints = constraints.addHeight(new Constraints(new Size(0, this._headerElement.offsetHeight)));
+ return constraints;
},
_updateTabElements: function()
diff --git a/Source/devtools/front_end/Target.js b/Source/devtools/front_end/Target.js
index 46df2e8..3cd2ec5 100644
--- a/Source/devtools/front_end/Target.js
+++ b/Source/devtools/front_end/Target.js
@@ -61,10 +61,14 @@
if (!WebInspector.runtimeModel)
WebInspector.runtimeModel = this.runtimeModel;
- this.domModel = new WebInspector.DOMModel();
+ this.domModel = new WebInspector.DOMModel(this);
if (!WebInspector.domModel)
WebInspector.domModel = this.domModel;
+ this.cssModel = new WebInspector.CSSStyleModel(this);
+ if (!WebInspector.cssModel)
+ WebInspector.cssModel = this.cssModel;
+
this.workerManager = new WebInspector.WorkerManager(this, this.isMainFrontend);
if (!WebInspector.workerManager)
WebInspector.workerManager = this.workerManager;
@@ -99,13 +103,54 @@
/**
* @constructor
+ * @param {!WebInspector.Target} target
+ */
+WebInspector.TargetAware = function(target)
+{
+ this._target = target;
+}
+
+WebInspector.TargetAware.prototype = {
+ /**
+ * @return {!WebInspector.Target}
+ */
+ target: function()
+ {
+ return this._target;
+ }
+}
+
+/**
+ * @constructor
* @extends {WebInspector.Object}
+ * @param {!WebInspector.Target} target
+ */
+WebInspector.TargetAwareObject = function(target)
+{
+ WebInspector.Object.call(this);
+ this._target = target;
+}
+
+WebInspector.TargetAwareObject.prototype = {
+ /**
+ * @return {!WebInspector.Target}
+ */
+ target: function()
+ {
+ return this._target;
+ },
+
+ __proto__: WebInspector.Object.prototype
+}
+
+/**
+ * @constructor
*/
WebInspector.TargetManager = function()
{
- WebInspector.Object.call(this);
/** @type {!Array.<!WebInspector.Target>} */
this._targets = [];
+ this._observers = [];
}
WebInspector.TargetManager.Events = {
@@ -115,6 +160,17 @@
WebInspector.TargetManager.prototype = {
/**
+ * @param {!WebInspector.TargetManager.Observer} targetObserver
+ */
+ observeTargets: function(targetObserver)
+ {
+ WebInspector.targetManager.targets().forEach(targetObserver.targetAdded.bind(targetObserver));
+ if (this._targets.length)
+ targetObserver.activeTargetChanged(this._targets[0]);
+ this._observers.push(targetObserver);
+ },
+
+ /**
* @param {!InspectorBackendClass.Connection} connection
* @param {function(!WebInspector.Target)=} callback
*/
@@ -132,9 +188,13 @@
callback(newTarget);
this._targets.push(newTarget);
- this.dispatchEventToListeners(WebInspector.TargetManager.Events.TargetAdded, newTarget);
+ var copy = this._observers;
+ for (var i = 0; i < copy.length; ++i) {
+ copy[i].targetAdded(newTarget);
+ if (this._targets.length === 1)
+ copy[i].activeTargetChanged(newTarget);
+ }
}
-
},
/**
@@ -146,14 +206,36 @@
},
/**
- * @return {!WebInspector.Target}
+ * @return {?WebInspector.Target}
*/
- mainTarget: function()
+ activeTarget: function()
{
return this._targets[0];
- },
+ }
+}
- __proto__: WebInspector.Object.prototype
+/**
+ * @interface
+ */
+WebInspector.TargetManager.Observer = function()
+{
+}
+
+WebInspector.TargetManager.Observer.prototype = {
+ /**
+ * @param {!WebInspector.Target} target
+ */
+ targetAdded: function(target) { },
+
+ /**
+ * @param {!WebInspector.Target} target
+ */
+ targetRemoved: function(target) { },
+
+ /**
+ * @param {?WebInspector.Target} target
+ */
+ activeTargetChanged: function(target) { }
}
/**
diff --git a/Source/devtools/front_end/TextRange.js b/Source/devtools/front_end/TextRange.js
index b7b6c05..30a2972 100644
--- a/Source/devtools/front_end/TextRange.js
+++ b/Source/devtools/front_end/TextRange.js
@@ -89,6 +89,16 @@
},
/**
+ * @param {!WebInspector.TextRange} range
+ * @return {boolean}
+ */
+ follows: function(range)
+ {
+ return (range.endLine === this.startLine && range.endColumn <= this.startColumn)
+ || range.endLine < this.startLine;
+ },
+
+ /**
* @return {number}
*/
get linesCount()
@@ -173,6 +183,29 @@
},
/**
+ * @param {!WebInspector.TextRange} originalRange
+ * @param {!WebInspector.TextRange} editedRange
+ * @return {!WebInspector.TextRange}
+ */
+ rebaseAfterTextEdit: function(originalRange, editedRange)
+ {
+ console.assert(originalRange.startLine === editedRange.startLine);
+ console.assert(originalRange.startColumn === editedRange.startColumn);
+ var rebase = this.clone();
+ if (!this.follows(originalRange))
+ return rebase;
+ var lineDelta = editedRange.endLine - originalRange.endLine;
+ var columnDelta = editedRange.endColumn - originalRange.endColumn;
+ rebase.startLine += lineDelta;
+ rebase.endLine += lineDelta;
+ if (rebase.startLine === editedRange.endLine)
+ rebase.startColumn += columnDelta;
+ if (rebase.endLine === editedRange.endLine)
+ rebase.endColumn += columnDelta;
+ return rebase;
+ },
+
+ /**
* @return {string}
*/
toString: function()
diff --git a/Source/devtools/front_end/TimelineFlameChart.js b/Source/devtools/front_end/TimelineFlameChart.js
index fea9a29..b007d0f 100644
--- a/Source/devtools/front_end/TimelineFlameChart.js
+++ b/Source/devtools/front_end/TimelineFlameChart.js
@@ -88,7 +88,7 @@
return WebInspector.UIString("CPU");
else if (record === this._gpuThreadRecord)
return WebInspector.UIString("GPU");
- var details = WebInspector.TimelineUIUtils.buildDetailsNode(record, this._linkifier);
+ var details = WebInspector.TimelineUIUtils.buildDetailsNode(record, this._linkifier, this._model.loadedFromFile());
return details ? WebInspector.UIString("%s (%s)", record.title(), details.textContent) : record.title();
},
@@ -421,6 +421,14 @@
}
WebInspector.TimelineFlameChart.prototype = {
+ timelineStarted: function()
+ {
+ },
+
+ timelineStopped: function()
+ {
+ },
+
/**
* @param {number} windowStartTime
* @param {number} windowEndTime
diff --git a/Source/devtools/front_end/TimelineFrameModel.js b/Source/devtools/front_end/TimelineFrameModel.js
index 9931b55..e37e102 100644
--- a/Source/devtools/front_end/TimelineFrameModel.js
+++ b/Source/devtools/front_end/TimelineFrameModel.js
@@ -124,32 +124,65 @@
},
/**
+ * @param {number} startTime
+ */
+ handleBeginFrame: function(startTime)
+ {
+ if (!this._lastFrame)
+ this._startBackgroundFrame(startTime);
+ },
+
+ /**
+ * @param {number} startTime
+ */
+ handleDrawFrame: function(startTime)
+ {
+ if (!this._lastFrame) {
+ this._startBackgroundFrame(startTime);
+ return;
+ }
+
+ // - if it wasn't drawn, it didn't happen!
+ // - only show frames that either did not wait for the main thread frame or had one committed.
+ if (this._mainFrameCommitted || !this._mainFrameRequested)
+ this._startBackgroundFrame(startTime);
+ this._mainFrameCommitted = false;
+ },
+
+ handleActivateLayerTree: function()
+ {
+ if (!this._lastFrame)
+ return;
+ this._mainFrameRequested = false;
+ this._mainFrameCommitted = true;
+ this._lastFrame._addTimeForCategories(this._aggregatedMainThreadWorkToAttachToBackgroundFrame);
+ this._aggregatedMainThreadWorkToAttachToBackgroundFrame = {};
+ },
+
+ handleRequestMainThreadFrame: function()
+ {
+ if (!this._lastFrame)
+ return;
+ this._mainFrameRequested = true;
+ },
+
+ /**
* @param {!WebInspector.TimelineModel.Record} record
*/
_addBackgroundRecord: function(record)
{
var recordTypes = WebInspector.TimelineModel.RecordType;
- if (!this._lastFrame) {
- if (record.type === recordTypes.BeginFrame || record.type === recordTypes.DrawFrame)
- this._startBackgroundFrame(record);
- return;
- }
+ if (record.type === recordTypes.BeginFrame)
+ this.handleBeginFrame(record.startTime);
+ else if (record.type === recordTypes.DrawFrame)
+ this.handleDrawFrame(record.startTime);
+ else if (record.type === recordTypes.RequestMainThreadFrame)
+ this.handleRequestMainThreadFrame();
+ else if (record.type === recordTypes.ActivateLayerTree)
+ this.handleActivateLayerTree();
- if (record.type === recordTypes.DrawFrame) {
- // - if it wasn't drawn, it didn't happen!
- // - only show frames that either did not wait for the main thread frame or had one committed.
- if (this._mainFrameCommitted || !this._mainFrameRequested)
- this._startBackgroundFrame(record);
- this._mainFrameCommitted = false;
- } else if (record.type === recordTypes.RequestMainThreadFrame) {
- this._mainFrameRequested = true;
- } else if (record.type === recordTypes.ActivateLayerTree) {
- this._mainFrameRequested = false;
- this._mainFrameCommitted = true;
- this._lastFrame._addTimeForCategories(this._aggregatedMainThreadWorkToAttachToBackgroundFrame);
- this._aggregatedMainThreadWorkToAttachToBackgroundFrame = {};
- }
- this._lastFrame._addTimeFromRecord(record);
+ if (this._lastFrame)
+ this._lastFrame._addTimeFromRecord(record);
},
/**
@@ -163,7 +196,7 @@
this._lastLayerTree = record.data["layerTree"] || null;
if (!this._hasThreadedCompositing) {
if (record.type === recordTypes.BeginFrame)
- this._startMainThreadFrame(record);
+ this._startMainThreadFrame(record.startTime);
if (!this._lastFrame)
return;
@@ -205,38 +238,38 @@
},
/**
- * @param {!WebInspector.TimelineModel.Record} record
+ * @param {number} startTime
*/
- _startBackgroundFrame: function(record)
+ _startBackgroundFrame: function(startTime)
{
if (!this._hasThreadedCompositing) {
this._lastFrame = null;
this._hasThreadedCompositing = true;
}
if (this._lastFrame)
- this._flushFrame(this._lastFrame, record);
+ this._flushFrame(this._lastFrame, startTime);
- this._lastFrame = new WebInspector.TimelineFrame(record);
+ this._lastFrame = new WebInspector.TimelineFrame(startTime, startTime - this._model.minimumRecordTime());
},
/**
- * @param {!WebInspector.TimelineModel.Record} record
+ * @param {number} startTime
*/
- _startMainThreadFrame: function(record)
+ _startMainThreadFrame: function(startTime)
{
if (this._lastFrame)
- this._flushFrame(this._lastFrame, record);
- this._lastFrame = new WebInspector.TimelineFrame(record);
+ this._flushFrame(this._lastFrame, startTime);
+ this._lastFrame = new WebInspector.TimelineFrame(startTime, startTime - this._model.minimumRecordTime());
},
/**
* @param {!WebInspector.TimelineFrame} frame
- * @param {!Object} record
+ * @param {number} endTime
*/
- _flushFrame: function(frame, record)
+ _flushFrame: function(frame, endTime)
{
frame._setLayerTree(this._lastLayerTree);
- frame._setEndTime(record.startTime);
+ frame._setEndTime(endTime);
this._frames.push(frame);
this.dispatchEventToListeners(WebInspector.TimelineFrameModel.Events.FrameAdded, frame);
},
@@ -294,12 +327,13 @@
/**
* @constructor
- * @param {!Object} record
+ * @param {number} startTime
+ * @param {number} startTimeOffset
*/
-WebInspector.TimelineFrame = function(record)
+WebInspector.TimelineFrame = function(startTime, startTimeOffset)
{
- this.startTime = record.startTime;
- this.startTimeOffset = record.startTimeOffset;
+ this.startTime = startTime;
+ this.startTimeOffset = startTimeOffset;
this.endTime = this.startTime;
this.duration = 0;
this.timeByCategory = {};
diff --git a/Source/devtools/front_end/TimelineModel.js b/Source/devtools/front_end/TimelineModel.js
index f0655a2..779f4fe 100644
--- a/Source/devtools/front_end/TimelineModel.js
+++ b/Source/devtools/front_end/TimelineModel.js
@@ -150,6 +150,14 @@
WebInspector.TimelineModel.prototype = {
/**
+ * @return {boolean}
+ */
+ loadedFromFile: function()
+ {
+ return this._loadedFromFile;
+ },
+
+ /**
* @param {?function(!WebInspector.TimelineModel.Record)|?function(!WebInspector.TimelineModel.Record,number)} preOrderCallback
* @param {function(!WebInspector.TimelineModel.Record)|function(!WebInspector.TimelineModel.Record,number)=} postOrderCallback
*/
@@ -423,6 +431,7 @@
reset: function()
{
+ this._loadedFromFile = false;
this._records = [];
this._payloads = [];
this._stringPool = {};
@@ -566,7 +575,8 @@
this._relatedBackendNodeId = record.data["rootNode"];
else if (record.data["elementId"])
this._relatedBackendNodeId = record.data["elementId"];
- if (record.data["scriptName"]) {
+ if (record.data["scriptName"] || record.data["scriptId"]) {
+ this.scriptId = record.data["scriptId"];
this.scriptName = record.data["scriptName"];
this.scriptLine = record.data["scriptLine"];
}
@@ -692,6 +702,14 @@
}
WebInspector.TimelineModel.Record.prototype = {
+ /**
+ * @return {!WebInspector.TimelineModel}
+ */
+ model: function()
+ {
+ return this._model;
+ },
+
get lastChildEndTime()
{
return this._lastChildEndTime;
@@ -1025,7 +1043,10 @@
this._model._addRecord(items[i]);
},
- close: function() { }
+ close: function()
+ {
+ this._model._loadedFromFile = true;
+ }
}
/**
diff --git a/Source/devtools/front_end/TimelinePanel.js b/Source/devtools/front_end/TimelinePanel.js
index 6e8c9ab..54ddeb1 100644
--- a/Source/devtools/front_end/TimelinePanel.js
+++ b/Source/devtools/front_end/TimelinePanel.js
@@ -44,6 +44,7 @@
importScript("TimelineFlameChart.js");
importScript("TimelineUIUtils.js");
importScript("TimelineView.js");
+importScript("TimelineTracingView.js");
/**
* @constructor
@@ -89,6 +90,8 @@
this._presentationModes.push(WebInspector.TimelinePanel.Mode.FlameChart);
if (Capabilities.canProfilePower)
this._presentationModes.push(WebInspector.TimelinePanel.Mode.Power);
+ if (WebInspector.experimentsSettings.timelineTracingMode.isEnabled())
+ this._presentationModes.push(WebInspector.TimelinePanel.Mode.Tracing);
this._presentationModeSetting = WebInspector.settings.createSetting("timelineOverviewMode", WebInspector.TimelinePanel.Mode.Events);
@@ -142,7 +145,8 @@
Frames: "Frames",
Memory: "Memory",
FlameChart: "FlameChart",
- Power: "Power"
+ Power: "Power",
+ Tracing: "Tracing"
};
// Define row and header height, should be in sync with styles for timeline graphs.
@@ -243,6 +247,16 @@
},
/**
+ * @return {!WebInspector.TimelineTracingView}
+ */
+ _tracingView: function()
+ {
+ if (!this._lazyTracingView)
+ this._lazyTracingView = new WebInspector.TimelineTracingView(this);
+ return this._lazyTracingView;
+ },
+
+ /**
* @return {!WebInspector.TimelineView}
*/
_timelineView: function()
@@ -282,6 +296,10 @@
views.overviewView = new WebInspector.TimelinePowerOverview(this._model);
views.mainViews = [this._timelineView(), new WebInspector.TimelinePowerGraph(this, this._model)];
break;
+ case WebInspector.TimelinePanel.Mode.Tracing:
+ views.overviewView = new WebInspector.TimelineFrameOverview(this._model, this._frameModel());
+ views.mainViews = [this._tracingView()];
+ break;
default:
console.assert(false, "Unknown mode: " + mode);
}
@@ -290,7 +308,6 @@
this._viewsMap[mode] = views;
}
- this._timelineView().setFrameModel(mode === WebInspector.TimelinePanel.Mode.Frames ? this._frameModel() : null);
return views;
},
@@ -555,6 +572,7 @@
this._stackView.appendView(view, "timelinePanelTimelineStackSplitViewState");
view.refreshRecords(this._textFilter._regex);
}
+ this._timelineView().setFrameModel(mode === WebInspector.TimelinePanel.Mode.Frames ? this._frameModel() : null);
this._overviewControl = views.overviewView;
this._overviewPane.setOverviewControl(this._overviewControl);
this._updateSelectionDetails();
@@ -567,9 +585,12 @@
{
this._userInitiatedRecording = userInitiated;
this._model.startRecording();
- for (var i = 0; i < this._presentationModes.length; ++i)
- this._viewsForMode(this._presentationModes[i]).overviewView.timelineStarted();
-
+ for (var i = 0; i < this._presentationModes.length; ++i) {
+ var views = this._viewsForMode(this._presentationModes[i]);
+ views.overviewView.timelineStarted();
+ for (var j = 0; j < views.mainViews.length; ++j)
+ views.mainViews[j].timelineStarted();
+ }
if (userInitiated)
WebInspector.userMetrics.TimelineStarted.record();
},
@@ -578,8 +599,12 @@
{
this._userInitiatedRecording = false;
this._model.stopRecording();
- for (var i = 0; i < this._presentationModes.length; ++i)
- this._viewsForMode(this._presentationModes[i]).overviewView.timelineStopped();
+ for (var i = 0; i < this._presentationModes.length; ++i) {
+ var views = this._viewsForMode(this._presentationModes[i]);
+ views.overviewView.timelineStopped();
+ for (var j = 0; j < views.mainViews.length; ++j)
+ views.mainViews[j].timelineStopped();
+ }
},
/**
@@ -894,7 +919,7 @@
this._updateSelectionDetails();
return;
}
- WebInspector.TimelineUIUtils.generatePopupContent(record, this._detailsLinkifier, showCallback.bind(this));
+ WebInspector.TimelineUIUtils.generatePopupContent(record, this._detailsLinkifier, showCallback.bind(this), this._model.loadedFromFile());
/**
* @param {!DocumentFragment} element
@@ -908,11 +933,11 @@
/**
* @param {string} title
- * @param {!Object} aggregatedStats
+ * @param {!Node} node
*/
- showAggregatedStatsInDetails: function(title, aggregatedStats)
+ showInDetails: function(title, node)
{
- this._detailsView.setContent(title, WebInspector.TimelineUIUtils.generatePieChart(aggregatedStats));
+ this._detailsView.setContent(title, node);
},
__proto__: WebInspector.Panel.prototype
@@ -1004,7 +1029,11 @@
/**
* @param {?WebInspector.TimelineModel.Record} record
*/
- setSelectedRecord: function(record) {}
+ setSelectedRecord: function(record) {},
+
+ timelineStarted: function() {},
+
+ timelineStopped: function() {},
}
/**
@@ -1026,9 +1055,9 @@
/**
* @param {string} title
- * @param {!Object} aggregatedStats
+ * @param {!Node} node
*/
- showAggregatedStatsInDetails: function(title, aggregatedStats) {},
+ showInDetails: function(title, node) {},
}
/**
diff --git a/Source/devtools/front_end/TimelinePowerGraph.js b/Source/devtools/front_end/TimelinePowerGraph.js
index 952011d..07566f2 100644
--- a/Source/devtools/front_end/TimelinePowerGraph.js
+++ b/Source/devtools/front_end/TimelinePowerGraph.js
@@ -18,6 +18,14 @@
}
WebInspector.TimelinePowerGraph.prototype = {
+ timelineStarted: function()
+ {
+ },
+
+ timelineStopped: function()
+ {
+ },
+
_onRecordAdded: function(event)
{
var record = event.data;
diff --git a/Source/devtools/front_end/TimelineTracingView.js b/Source/devtools/front_end/TimelineTracingView.js
new file mode 100644
index 0000000..9a89555
--- /dev/null
+++ b/Source/devtools/front_end/TimelineTracingView.js
@@ -0,0 +1,527 @@
+/*
+ * Copyright 2014 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.
+ */
+
+/**
+ * @constructor
+ * @implements {WebInspector.TimelineModeView}
+ * @implements {WebInspector.FlameChartDelegate}
+ * @extends {WebInspector.VBox}
+ * @param {!WebInspector.TimelineModeViewDelegate} delegate
+ */
+WebInspector.TimelineTracingView = function(delegate)
+{
+ WebInspector.VBox.call(this);
+ this._delegate = delegate;
+ this._tracingModel = new WebInspector.TracingModel();
+ this.element.classList.add("timeline-flamechart");
+ this.registerRequiredCSS("flameChart.css");
+ this._dataProvider = new WebInspector.TraceViewFlameChartDataProvider(this._tracingModel);
+ this._mainView = new WebInspector.FlameChart(this._dataProvider, this, true, true);
+ this._mainView.show(this.element);
+ this._mainView.addEventListener(WebInspector.FlameChart.Events.EntrySelected, this._onEntrySelected, this);
+}
+
+WebInspector.TimelineTracingView.prototype = {
+ timelineStarted: function()
+ {
+ if (this._recordingTrace)
+ return;
+ if (!this._boundTraceEventListener)
+ this._boundTraceEventListener = this._onTraceEventsCollected.bind(this);
+ this._recordingTrace = true;
+ WebInspector.tracingAgent.addEventListener(WebInspector.TracingAgent.Events.EventsCollected, this._boundTraceEventListener);
+ this._tracingModel.reset();
+ WebInspector.tracingAgent.start("", "");
+ },
+
+ timelineStopped: function()
+ {
+ if (!this._recordingTrace)
+ return;
+
+ /**
+ * @this {WebInspector.TimelineTracingView}
+ */
+ function onTraceDataComplete()
+ {
+ WebInspector.tracingAgent.removeEventListener(WebInspector.TracingAgent.Events.EventsCollected, this._boundTraceEventListener);
+ this.refreshRecords(null);
+ }
+ WebInspector.tracingAgent.stop(onTraceDataComplete.bind(this));
+ this._recordingTrace = false;
+ },
+
+ /**
+ * @param {!WebInspector.Event} event
+ */
+ _onTraceEventsCollected: function(event)
+ {
+ var events = /** @type {!Array.<!WebInspector.TracingAgent.Event>} */ (event.data);
+ this._tracingModel.addEvents(events);
+ },
+
+ /**
+ * @param {number} windowStartTime
+ * @param {number} windowEndTime
+ */
+ requestWindowTimes: function(windowStartTime, windowEndTime)
+ {
+ this._delegate.requestWindowTimes(windowStartTime, windowEndTime);
+ },
+
+ wasShown: function()
+ {
+ this._mainView._scheduleUpdate();
+ },
+
+ reset: function()
+ {
+ this._dataProvider.reset();
+ this._mainView.setWindowTimes(0, Infinity);
+ },
+
+ /**
+ * @param {?RegExp} textFilter
+ */
+ refreshRecords: function(textFilter)
+ {
+ this._dataProvider.reset();
+ this._mainView._scheduleUpdate();
+ },
+
+ /**
+ * @param {!WebInspector.TimelineModel.Record} record
+ */
+ addRecord: function(record) {},
+
+ /**
+ * @param {?WebInspector.TimelineModel.Record} record
+ * @param {string=} regex
+ * @param {boolean=} selectRecord
+ */
+ highlightSearchResult: function(record, regex, selectRecord) {},
+
+ /**
+ * @param {number} startTime
+ * @param {number} endTime
+ */
+ setWindowTimes: function(startTime, endTime)
+ {
+ this._mainView.setWindowTimes(startTime, endTime);
+ },
+
+ /**
+ * @param {number} width
+ */
+ setSidebarSize: function(width) {},
+
+ /**
+ * @param {?WebInspector.TimelineModel.Record} record
+ */
+ setSelectedRecord: function(record) {},
+
+ /**
+ * @param {!WebInspector.Event} event
+ */
+ _onEntrySelected: function(event)
+ {
+ var index = /** @type {number} */ (event.data);
+ var record = this._dataProvider._recordAt(index);
+ if (!record || this._dataProvider._isHeaderRecord(record)) {
+ this._delegate.showInDetails("", document.createTextNode(""));
+ return;
+ }
+ var contentHelper = new WebInspector.TimelineDetailsContentHelper(new WebInspector.Linkifier(), false);
+ contentHelper.appendTextRow(WebInspector.UIString("Name"), record.name);
+ contentHelper.appendTextRow(WebInspector.UIString("Category"), record.category);
+ contentHelper.appendTextRow(WebInspector.UIString("Start"), Number.millisToString(this._dataProvider._toTimelineTime(record.startTime - this._tracingModel.minimumRecordTime()), true));
+ contentHelper.appendTextRow(WebInspector.UIString("Duration"), Number.millisToString(this._dataProvider._toTimelineTime(record.duration), true));
+ if (!Object.isEmpty(record.args)) {
+ var table = document.createElement("table");
+ for (var name in record.args) {
+ var row = table.createChild("tr");
+ row.createChild("td", "timeline-details-row-title").textContent = name + ":";
+ row.createChild("td", "timeline-details-row-data").textContent = record.args[name];
+ }
+ contentHelper.appendElementRow(WebInspector.UIString("Arguments"), table);
+ }
+ this._delegate.showInDetails(WebInspector.UIString("Selected Event"), contentHelper.element);
+ },
+
+ __proto__: WebInspector.VBox.prototype
+};
+
+/**
+ * @constructor
+ * @implements {WebInspector.FlameChartDataProvider}
+ * @param {!WebInspector.TracingModel} model
+ */
+WebInspector.TraceViewFlameChartDataProvider = function(model)
+{
+ WebInspector.FlameChartDataProvider.call(this);
+ this._model = model;
+ this._font = "bold 12px " + WebInspector.fontFamily();
+ this._palette = new WebInspector.TraceViewPalette();
+ var dummyEventPayload = {
+ cat: "dummy",
+ pid: 0,
+ tid: 0,
+ ts: 0,
+ ph: "dummy",
+ name: "dummy",
+ args: {},
+ dur: 0,
+ id: 0,
+ s: ""
+ }
+ this._processHeaderRecord = new WebInspector.TracingModel.Event(dummyEventPayload, 0);
+ this._threadHeaderRecord = new WebInspector.TracingModel.Event(dummyEventPayload, 0);
+}
+
+WebInspector.TraceViewFlameChartDataProvider.prototype = {
+ /**
+ * @return {number}
+ */
+ barHeight: function()
+ {
+ return 20;
+ },
+
+ /**
+ * @return {number}
+ */
+ textBaseline: function()
+ {
+ return 6;
+ },
+
+ /**
+ * @return {number}
+ */
+ textPadding: function()
+ {
+ return 5;
+ },
+
+ /**
+ * @param {number} entryIndex
+ * @return {string}
+ */
+ entryFont: function(entryIndex)
+ {
+ return this._font;
+ },
+
+ /**
+ * @param {number} entryIndex
+ * @return {?string}
+ */
+ entryTitle: function(entryIndex)
+ {
+ var record = this._records[entryIndex];
+ if (this._isHeaderRecord(record))
+ return this._headerTitles[entryIndex]
+ return record.name;
+ },
+
+ /**
+ * @param {number} startTime
+ * @param {number} endTime
+ * @return {?Array.<number>}
+ */
+ dividerOffsets: function(startTime, endTime)
+ {
+ return null;
+ },
+
+ reset: function()
+ {
+ this._timelineData = null;
+ /** @type {!Array.<!WebInspector.TracingModel.Event>} */
+ this._records = [];
+ },
+
+ /**
+ * @return {!WebInspector.FlameChart.TimelineData}
+ */
+ timelineData: function()
+ {
+ if (this._timelineData)
+ return this._timelineData;
+
+ /**
+ * @type {?WebInspector.FlameChart.TimelineData}
+ */
+ this._timelineData = {
+ entryLevels: [],
+ entryTotalTimes: [],
+ entryOffsets: []
+ };
+
+ this._currentLevel = 0;
+ this._headerTitles = {};
+ this._zeroTime = this._model.minimumRecordTime() || 0;
+ this._timeSpan = Math.max((this._model.maximumRecordTime() || 0) - this._zeroTime, 1000000);
+ var processes = this._model.sortedProcesses();
+ for (var i = 0; i < processes.length; ++i) {
+ this._appendHeaderRecord(processes[i].name(), this._processHeaderRecord);
+ var threads = processes[i].sortedThreads();
+ for (var j = 0; j < threads.length; ++j) {
+ this._appendHeaderRecord(threads[j].name(), this._threadHeaderRecord);
+ var events = threads[j].events();
+ for (var k = 0; k < events.length; ++k) {
+ if (events[k].duration)
+ this._appendRecord(events[k]);
+ }
+ this._currentLevel += threads[j].maxStackDepth();
+ }
+ ++this._currentLevel;
+ }
+ return this._timelineData;
+ },
+
+ /**
+ * @return {number}
+ */
+ zeroTime: function()
+ {
+ return this._toTimelineTime(this._zeroTime);
+ },
+
+ /**
+ * @return {number}
+ */
+ totalTime: function()
+ {
+ return this._toTimelineTime(this._timeSpan);
+ },
+
+ /**
+ * @return {number}
+ */
+ maxStackDepth: function()
+ {
+ return this._currentLevel;
+ },
+
+ /**
+ * @param {number} entryIndex
+ * @return {?Array.<!{title: string, text: string}>}
+ */
+ prepareHighlightedEntryInfo: function(entryIndex)
+ {
+ return null;
+ },
+
+ /**
+ * @param {number} entryIndex
+ * @return {boolean}
+ */
+ canJumpToEntry: function(entryIndex)
+ {
+ return false;
+ },
+
+ /**
+ * @param {number} entryIndex
+ * @return {!string}
+ */
+ entryColor: function(entryIndex)
+ {
+ var record = this._records[entryIndex];
+ if (record === this._processHeaderRecord)
+ return "#555";
+ if (record === this._threadHeaderRecord)
+ return "#777";
+ return this._palette.colorForString(record.name);
+ },
+
+
+ /**
+ * @param {number} entryIndex
+ * @param {!CanvasRenderingContext2D} context
+ * @param {?string} text
+ * @param {number} barX
+ * @param {number} barY
+ * @param {number} barWidth
+ * @param {number} barHeight
+ * @param {function(number):number} offsetToPosition
+ * @return {boolean}
+ */
+ decorateEntry: function(entryIndex, context, text, barX, barY, barWidth, barHeight, offsetToPosition)
+ {
+ return false;
+ },
+
+ /**
+ * @param {number} entryIndex
+ * @return {boolean}
+ */
+ forceDecoration: function(entryIndex)
+ {
+ return false;
+ },
+
+ /**
+ * @param {number} entryIndex
+ * @return {?{startTimeOffset: number, endTimeOffset: number}}
+ */
+ highlightTimeRange: function(entryIndex)
+ {
+ var record = this._records[entryIndex];
+ if (!record || this._isHeaderRecord(record))
+ return null;
+ return {
+ startTimeOffset: this._toTimelineTime(record.startTime - this._zeroTime),
+ endTimeOffset: this._toTimelineTime(record.endTime - this._zeroTime)
+ }
+ },
+
+ /**
+ * @return {number}
+ */
+ paddingLeft: function()
+ {
+ return 0;
+ },
+
+ /**
+ * @param {number} entryIndex
+ * @return {!string}
+ */
+ textColor: function(entryIndex)
+ {
+ return "white";
+ },
+
+ /**
+ * @param {string} title
+ * @param {!WebInspector.TracingModel.Event} record
+ */
+ _appendHeaderRecord: function(title, record)
+ {
+ var index = this._records.length;
+ this._records.push(record);
+ this._timelineData.entryLevels[index] = this._currentLevel++;
+ this._timelineData.entryTotalTimes[index] = this.totalTime();
+ this._timelineData.entryOffsets[index] = this._toTimelineTime(0);
+ this._headerTitles[index] = title;
+ },
+
+ /**
+ * @param {!WebInspector.TracingModel.Event} record
+ */
+ _appendRecord: function(record)
+ {
+ var index = this._records.length;
+ this._records.push(record);
+ this._timelineData.entryLevels[index] = this._currentLevel + record.level;
+ this._timelineData.entryTotalTimes[index] = this._toTimelineTime(record.duration);
+ this._timelineData.entryOffsets[index] = this._toTimelineTime(record.startTime - this._zeroTime);
+ },
+
+ /**
+ * @param {number} time
+ * @return {number}
+ */
+ _toTimelineTime: function(time)
+ {
+ return time / 1000;
+ },
+
+ /**
+ * @param {!WebInspector.TracingModel.Event} record
+ */
+ _isHeaderRecord: function(record)
+ {
+ return record === this._threadHeaderRecord || record === this._processHeaderRecord;
+ },
+
+ /**
+ * @param {number} index
+ * @return {!WebInspector.TracingModel.Event|undefined}
+ */
+ _recordAt: function(index)
+ {
+ return this._records[index];
+ }
+}
+
+// The below logic is shamelessly stolen from https://code.google.com/p/trace-viewer/source/browse/trunk/trace_viewer/tracing/color_scheme.js
+
+/**
+ * @constructor
+ */
+WebInspector.TraceViewPalette = function()
+{
+ this._palette = WebInspector.TraceViewPalette._paletteBase.map(WebInspector.TraceViewPalette._rgbToString);
+}
+
+WebInspector.TraceViewPalette._paletteBase = [
+ [138, 113, 152],
+ [175, 112, 133],
+ [127, 135, 225],
+ [93, 81, 137],
+ [116, 143, 119],
+ [178, 214, 122],
+ [87, 109, 147],
+ [119, 155, 95],
+ [114, 180, 160],
+ [132, 85, 103],
+ [157, 210, 150],
+ [148, 94, 86],
+ [164, 108, 138],
+ [139, 191, 150],
+ [110, 99, 145],
+ [80, 129, 109],
+ [125, 140, 149],
+ [93, 124, 132],
+ [140, 85, 140],
+ [104, 163, 162],
+ [132, 141, 178],
+ [131, 105, 147],
+ [135, 183, 98],
+ [152, 134, 177],
+ [141, 188, 141],
+ [133, 160, 210],
+ [126, 186, 148],
+ [112, 198, 205],
+ [180, 122, 195],
+ [203, 144, 152]
+];
+
+/**
+ * @param {string} string
+ * @return {number}
+ */
+WebInspector.TraceViewPalette._stringHash = function(string)
+{
+ var hash = 0;
+ for (var i = 0; i < string.length; ++i)
+ hash = (hash + 37 * hash + 11 * string.charCodeAt(i)) % 0xFFFFFFFF;
+ return hash;
+}
+
+/**
+ * @param {!Array.<number>} rgb
+ * @return {string}
+ */
+WebInspector.TraceViewPalette._rgbToString = function(rgb)
+{
+ return "rgb(" + rgb.join(",") + ")";
+}
+
+WebInspector.TraceViewPalette.prototype = {
+ /**
+ * @param {string} string
+ * @return {string}
+ */
+ colorForString: function(string)
+ {
+ var hash = WebInspector.TraceViewPalette._stringHash(string);
+ return this._palette[hash % this._palette.length];
+ }
+};
diff --git a/Source/devtools/front_end/TimelineUIUtils.js b/Source/devtools/front_end/TimelineUIUtils.js
index aefebd0..316432b 100644
--- a/Source/devtools/front_end/TimelineUIUtils.js
+++ b/Source/devtools/front_end/TimelineUIUtils.js
@@ -440,8 +440,9 @@
* @param {!WebInspector.TimelineModel.Record} record
* @param {!WebInspector.Linkifier} linkifier
* @param {function(!DocumentFragment)} callback
+ * @param {boolean} loadedFromFile
*/
-WebInspector.TimelineUIUtils.generatePopupContent = function(record, linkifier, callback)
+WebInspector.TimelineUIUtils.generatePopupContent = function(record, linkifier, callback, loadedFromFile)
{
var imageElement = /** @type {?Element} */ (record.getUserObject("TimelineUIUtils::preview-element") || null);
var relatedNode = null;
@@ -473,7 +474,7 @@
function callbackWrapper()
{
- callback(WebInspector.TimelineUIUtils._generatePopupContentSynchronously(record, linkifier, imageElement, relatedNode));
+ callback(WebInspector.TimelineUIUtils._generatePopupContentSynchronously(record, linkifier, imageElement, relatedNode, loadedFromFile));
}
}
@@ -482,9 +483,10 @@
* @param {!WebInspector.Linkifier} linkifier
* @param {?Element} imagePreviewElement
* @param {?WebInspector.DOMNode} relatedNode
+ * @param {boolean} loadedFromFile
* @return {!DocumentFragment}
*/
-WebInspector.TimelineUIUtils._generatePopupContentSynchronously = function(record, linkifier, imagePreviewElement, relatedNode)
+WebInspector.TimelineUIUtils._generatePopupContentSynchronously = function(record, linkifier, imagePreviewElement, relatedNode, loadedFromFile)
{
var fragment = document.createDocumentFragment();
if (record.children.length)
@@ -614,7 +616,7 @@
contentHelper.appendTextRow(WebInspector.UIString("Callback Function"), record.embedderCallbackName);
break;
default:
- var detailsNode = WebInspector.TimelineUIUtils.buildDetailsNode(record, linkifier);
+ var detailsNode = WebInspector.TimelineUIUtils.buildDetailsNode(record, linkifier, loadedFromFile);
if (detailsNode)
contentHelper.appendElementRow(WebInspector.UIString("Details"), detailsNode);
break;
@@ -672,9 +674,10 @@
/**
* @param {!WebInspector.TimelineModel.Record} record
* @param {!WebInspector.Linkifier} linkifier
+ * @param {boolean} loadedFromFile
* @return {?Node}
*/
-WebInspector.TimelineUIUtils.buildDetailsNode = function(record, linkifier)
+WebInspector.TimelineUIUtils.buildDetailsNode = function(record, linkifier, loadedFromFile)
{
var details;
var detailsText;
@@ -687,8 +690,7 @@
detailsText = record.data["timerId"];
break;
case WebInspector.TimelineModel.RecordType.FunctionCall:
- if (record.scriptName)
- details = linkifyLocation(record.scriptName, record.scriptLine, 0);
+ details = linkifyLocation(record.data.scriptId, record.data.scriptName, record.data.scriptLine, 0);
break;
case WebInspector.TimelineModel.RecordType.FireAnimationFrame:
detailsText = record.data["id"];
@@ -717,7 +719,7 @@
details = linkifyTopCallFrame();
break;
case WebInspector.TimelineModel.RecordType.EvaluateScript:
- details = record.url ? linkifyLocation(record.url, record.data["lineNumber"], 0) : null;
+ details = linkifyLocation("", record.url, record.data["lineNumber"], 0);
break;
case WebInspector.TimelineModel.RecordType.XHRReadyStateChange:
case WebInspector.TimelineModel.RecordType.XHRLoad:
@@ -737,7 +739,7 @@
detailsText = record.data["callbackName"];
break;
default:
- details = record.scriptName ? linkifyLocation(record.scriptName, record.scriptLine, 0) : linkifyTopCallFrame();
+ details = linkifyTopCallFrame();
break;
}
@@ -746,12 +748,25 @@
return details;
/**
+ * @param {string} scriptId
* @param {string} url
* @param {number} lineNumber
* @param {number=} columnNumber
*/
- function linkifyLocation(url, lineNumber, columnNumber)
+ function linkifyLocation(scriptId, url, lineNumber, columnNumber)
{
+ if (!loadedFromFile && scriptId !== "0") {
+ var location = new WebInspector.DebuggerModel.Location(
+ /** @type {!WebInspector.Target} */ (WebInspector.targetManager.activeTarget()),
+ scriptId,
+ lineNumber - 1,
+ (columnNumber || 1) - 1);
+ return linkifier.linkifyRawLocation(location, "timeline-details");
+ }
+
+ if (!url)
+ return null;
+
// FIXME(62725): stack trace line/column numbers are one-based.
columnNumber = columnNumber ? columnNumber - 1 : 0;
return linkifier.linkifyLocation(url, lineNumber - 1, columnNumber, "timeline-details");
@@ -762,7 +777,7 @@
*/
function linkifyCallFrame(callFrame)
{
- return linkifyLocation(callFrame.url, callFrame.lineNumber, callFrame.columnNumber);
+ return linkifyLocation(callFrame.scriptId, callFrame.url, callFrame.lineNumber, callFrame.columnNumber);
}
/**
@@ -776,14 +791,6 @@
return linkifyCallFrame(record.callSiteStackTrace[0]);
return null;
}
-
- /**
- * @return {?Element}
- */
- function linkifyScriptLocation()
- {
- return record.scriptName ? linkifyLocation(record.scriptName, record.scriptLine, 0) : null;
- }
}
/**
diff --git a/Source/devtools/front_end/TimelineView.js b/Source/devtools/front_end/TimelineView.js
index 2d37c4f..70da6c7 100644
--- a/Source/devtools/front_end/TimelineView.js
+++ b/Source/devtools/front_end/TimelineView.js
@@ -71,6 +71,14 @@
}
WebInspector.TimelineView.prototype = {
+ timelineStarted: function()
+ {
+ },
+
+ timelineStopped: function()
+ {
+ },
+
/**
* @param {?WebInspector.TimelineFrameModel} frameModel
*/
@@ -323,7 +331,8 @@
for (var category in aggregatedStats)
idle -= aggregatedStats[category];
aggregatedStats["idle"] = idle;
- this._delegate.showAggregatedStatsInDetails(WebInspector.TimelineUIUtils.recordStyle(presentationRecord.record()).title, aggregatedStats);
+ var pieChart = WebInspector.TimelineUIUtils.generatePieChart(aggregatedStats);
+ this._delegate.showInDetails(WebInspector.TimelineUIUtils.recordStyle(presentationRecord.record()).title, pieChart);
return;
}
this._delegate.selectRecord(presentationRecord ? presentationRecord.record() : null);
@@ -530,7 +539,7 @@
this._graphRowsElement.appendChild(graphRowElement);
}
- listRowElement.row.update(record, visibleTop);
+ listRowElement.row.update(record, visibleTop, this._model.loadedFromFile());
graphRowElement.row.update(record, this._calculator, this._expandOffset, i);
if (this._lastSelectedRecord === record) {
listRowElement.row.renderAsSelected(true);
@@ -996,8 +1005,9 @@
/**
* @param {!WebInspector.TimelinePresentationModel.Record} presentationRecord
* @param {number} offset
+ * @param {boolean} loadedFromFile
*/
- update: function(presentationRecord, offset)
+ update: function(presentationRecord, offset, loadedFromFile)
{
this._record = presentationRecord;
var record = presentationRecord.record();
@@ -1023,7 +1033,7 @@
if (presentationRecord.coalesced()) {
this._dataElement.createTextChild(WebInspector.UIString("× %d", presentationRecord.presentationChildren().length));
} else {
- var detailsNode = WebInspector.TimelineUIUtils.buildDetailsNode(record, this._linkifier);
+ var detailsNode = WebInspector.TimelineUIUtils.buildDetailsNode(record, this._linkifier, loadedFromFile);
if (detailsNode) {
this._dataElement.appendChild(document.createTextNode("("));
this._dataElement.appendChild(detailsNode);
diff --git a/Source/devtools/front_end/TracingAgent.js b/Source/devtools/front_end/TracingAgent.js
index e7ad664..964aad3 100644
--- a/Source/devtools/front_end/TracingAgent.js
+++ b/Source/devtools/front_end/TracingAgent.js
@@ -30,13 +30,64 @@
/**
* @constructor
+ * @extends {WebInspector.Object}
*/
WebInspector.TracingAgent = function()
{
+ WebInspector.Object.call(this);
this._active = false;
InspectorBackend.registerTracingDispatcher(new WebInspector.TracingDispatcher(this));
}
+WebInspector.TracingAgent.Events = {
+ EventsCollected: "EventsCollected"
+};
+
+/** @typedef {!{
+ cat: string,
+ pid: number,
+ tid: number,
+ ts: number,
+ ph: string,
+ name: string,
+ args: !Object,
+ dur: number,
+ id: number,
+ s: string
+ }}
+ */
+WebInspector.TracingAgent.Event;
+
+/**
+ * @enum {string}
+ */
+WebInspector.TracingAgent.Phase = {
+ Begin: "B",
+ End: "E",
+ Complete: "X",
+ Instant: "i",
+ AsyncBegin: "S",
+ AsyncStepInto: "T",
+ AsyncStepPast: "p",
+ AsyncEnd: "F",
+ FlowBegin: "s",
+ FlowStep: "t",
+ FlowEnd: "f",
+ Metadata: "M",
+ Counter: "C",
+ Sample: "P",
+ CreateObject: "N",
+ SnapshotObject: "O",
+ DeleteObject: "D"
+};
+
+WebInspector.TracingAgent.MetadataEvent = {
+ ProcessSortIndex: "process_sort_index",
+ ProcessName: "process_name",
+ ThreadSortIndex: "thread_sort_index",
+ ThreadName: "thread_name"
+}
+
WebInspector.TracingAgent.prototype = {
/**
* @param {string} categoryPatterns
@@ -47,7 +98,6 @@
{
TracingAgent.start(categoryPatterns, options, callback);
this._active = true;
- this._events = [];
},
/**
@@ -63,27 +113,21 @@
TracingAgent.end();
},
- /**
- * @return {!Array.<!{cat: string, args: !Object, ph: string, ts: number}>}
- */
- events: function()
- {
- return this._events;
- },
-
_eventsCollected: function(events)
{
- Array.prototype.push.apply(this._events, events);
+ this.dispatchEventToListeners(WebInspector.TracingAgent.Events.EventsCollected, events);
},
_tracingComplete: function()
{
this._active = false;
- if (this._pendingStopCallback) {
- this._pendingStopCallback();
- this._pendingStopCallback = null;
- }
- }
+ if (!this._pendingStopCallback)
+ return;
+ this._pendingStopCallback();
+ this._pendingStopCallback = null;
+ },
+
+ __proto__: WebInspector.Object.prototype
}
/**
diff --git a/Source/devtools/front_end/TracingModel.js b/Source/devtools/front_end/TracingModel.js
new file mode 100644
index 0000000..da3aff7
--- /dev/null
+++ b/Source/devtools/front_end/TracingModel.js
@@ -0,0 +1,289 @@
+/*
+ * Copyright 2014 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.
+ */
+
+/**
+ * @constructor
+ */
+WebInspector.TracingModel = function()
+{
+ this.reset();
+}
+
+WebInspector.TracingModel.prototype = {
+ reset: function()
+ {
+ this._processById = {};
+ this._minimumRecordTime = null;
+ this._maximumRecordTime = null;
+ },
+
+ /**
+ * @param {!Array.<!WebInspector.TracingAgent.Event>} payload
+ */
+ addEvents: function(payload)
+ {
+ for (var i = 0; i < payload.length; ++i)
+ this.addEvent(payload[i]);
+ },
+
+ /**
+ * @param {!WebInspector.TracingAgent.Event} payload
+ */
+ addEvent: function(payload)
+ {
+ var process = this._processById[payload.pid];
+ if (!process) {
+ process = new WebInspector.TracingModel.Process(payload.pid);
+ this._processById[payload.pid] = process;
+ }
+ var thread = process.threadById(payload.tid);
+ if (payload.ph !== WebInspector.TracingAgent.Phase.Metadata) {
+ var timestamp = payload.ts;
+ // We do allow records for unrelated threads to arrive out-of-order,
+ // so there's a chance we're getting records from the past.
+ if (timestamp && (!this._minimumRecordTime || timestamp < this._minimumRecordTime))
+ this._minimumRecordTime = timestamp;
+ if (!this._maximumRecordTime || timestamp > this._maximumRecordTime)
+ this._maximumRecordTime = timestamp;
+ thread.addEvent(payload);
+ return;
+ }
+ switch (payload.name) {
+ case WebInspector.TracingAgent.MetadataEvent.ProcessSortIndex:
+ process._setSortIndex(payload.args["sort_index"]);
+ break;
+ case WebInspector.TracingAgent.MetadataEvent.ProcessName:
+ process._setName(payload.args["name"]);
+ break;
+ case WebInspector.TracingAgent.MetadataEvent.ThreadSortIndex:
+ thread._setSortIndex(payload.args["sort_index"]);
+ break;
+ case WebInspector.TracingAgent.MetadataEvent.ThreadName:
+ thread._setName(payload.args["name"]);
+ break;
+ }
+ },
+
+ /**
+ * @return {?number}
+ */
+ minimumRecordTime: function()
+ {
+ return this._minimumRecordTime;
+ },
+
+ /**
+ * @return {?number}
+ */
+ maximumRecordTime: function()
+ {
+ return this._maximumRecordTime;
+ },
+
+ /**
+ * @return {!Array.<!WebInspector.TracingModel.Process>}
+ */
+ sortedProcesses: function()
+ {
+ return WebInspector.TracingModel.NamedObject._sort(Object.values(this._processById));
+ }
+}
+
+/**
+ * @constructor
+ * @param {!WebInspector.TracingAgent.Event} payload
+ * @param {number} level
+ */
+WebInspector.TracingModel.Event = function(payload, level)
+{
+ this.name = payload.name;
+ this.category = payload.cat;
+ this.startTime = payload.ts;
+ this.args = payload.args;
+ this.phase = payload.phase;
+ this.level = level;
+}
+
+WebInspector.TracingModel.Event.prototype = {
+ /**
+ * @param {number} duration
+ */
+ _setDuration: function(duration)
+ {
+ this.endTime = this.startTime + duration;
+ this.duration = duration;
+ },
+
+ /**
+ * @param {!WebInspector.TracingAgent.Event} payload
+ */
+ _complete: function(payload)
+ {
+ if (this.name !== payload.name) {
+ console.assert(false, "Open/close event mismatch: " + this.name + " vs. " + payload.name);
+ return;
+ }
+ var duration = payload.ts - this.startTime;
+ if (duration < 0) {
+ console.assert(false, "Event out of order: " + this.name);
+ return;
+ }
+ this._setDuration(duration);
+ }
+}
+
+/**
+ * @constructor
+ */
+WebInspector.TracingModel.NamedObject = function()
+{
+}
+
+WebInspector.TracingModel.NamedObject.prototype =
+{
+ /**
+ * @param {string} name
+ */
+ _setName: function(name)
+ {
+ this._name = name;
+ },
+
+ /**
+ * @return {string}
+ */
+ name: function()
+ {
+ return this._name;
+ },
+
+ /**
+ * @param {number} sortIndex
+ */
+ _setSortIndex: function(sortIndex)
+ {
+ this._sortIndex = sortIndex;
+ },
+}
+
+/**
+ * @param {!Array.<!WebInspector.TracingModel.NamedObject>} array
+ */
+WebInspector.TracingModel.NamedObject._sort = function(array)
+{
+ /**
+ * @param {!WebInspector.TracingModel.NamedObject} a
+ * @param {!WebInspector.TracingModel.NamedObject} b
+ */
+ function comparator(a, b)
+ {
+ return a._sortIndex !== b._sortIndex ? a._sortIndex - b._sortIndex : a.name().localeCompare(b.name());
+ }
+ return array.sort(comparator);
+}
+
+/**
+ * @constructor
+ * @extends {WebInspector.TracingModel.NamedObject}
+ * @param {number} id
+ */
+WebInspector.TracingModel.Process = function(id)
+{
+ WebInspector.TracingModel.NamedObject.call(this);
+ this._setName("Process " + id);
+ this._threads = {};
+}
+
+WebInspector.TracingModel.Process.prototype = {
+ /**
+ * @param {number} id
+ * @return {!WebInspector.TracingModel.Thread}
+ */
+ threadById: function(id)
+ {
+ var thread = this._threads[id];
+ if (!thread) {
+ thread = new WebInspector.TracingModel.Thread(id);
+ this._threads[id] = thread;
+ }
+ return thread;
+ },
+
+ /**
+ * @return {!Array.<!WebInspector.TracingModel.Thread>}
+ */
+ sortedThreads: function()
+ {
+ return WebInspector.TracingModel.NamedObject._sort(Object.values(this._threads));
+ },
+
+ __proto__: WebInspector.TracingModel.NamedObject.prototype
+}
+
+/**
+ * @constructor
+ * @extends {WebInspector.TracingModel.NamedObject}
+ * @param {number} id
+ */
+WebInspector.TracingModel.Thread = function(id)
+{
+ WebInspector.TracingModel.NamedObject.call(this);
+ this._setName("Thread " + id);
+ this._events = [];
+ this._stack = [];
+ this._maxStackDepth = 0;
+}
+
+WebInspector.TracingModel.Thread.prototype = {
+ /**
+ * @param {!WebInspector.TracingAgent.Event} payload
+ */
+ addEvent: function(payload)
+ {
+ for (var top = this._stack.peekLast(); top && top.endTime && top.endTime <= payload.ts;) {
+ this._stack.pop();
+ top = this._stack.peekLast();
+ }
+ if (payload.ph === WebInspector.TracingAgent.Phase.End) {
+ var openEvent = this._stack.pop();
+ // Quietly ignore unbalanced close events, they're legit (we could have missed start one).
+ if (openEvent)
+ openEvent._complete(payload);
+ return;
+ }
+
+ var event = new WebInspector.TracingModel.Event(payload, this._stack.length);
+ if (payload.ph === WebInspector.TracingAgent.Phase.Begin || payload.ph === WebInspector.TracingAgent.Phase.Complete) {
+ if (payload.ph === WebInspector.TracingAgent.Phase.Complete)
+ event._setDuration(payload.dur);
+ this._stack.push(event);
+ if (this._maxStackDepth < this._stack.length)
+ this._maxStackDepth = this._stack.length;
+ }
+ if (this._events.length && this._events.peekLast().startTime > event.startTime)
+ console.assert(false, "Event is our of order: " + event.name);
+ this._events.push(event);
+ },
+
+ /**
+ * @return {!Array.<!WebInspector.TracingModel.Event>}
+ */
+ events: function()
+ {
+ return this._events;
+ },
+
+ /**
+ * @return {number}
+ */
+ maxStackDepth: function()
+ {
+ // Reserve one for non-container events.
+ return this._maxStackDepth + 1;
+ },
+
+ __proto__: WebInspector.TracingModel.NamedObject.prototype
+}
diff --git a/Source/devtools/front_end/UISourceCode.js b/Source/devtools/front_end/UISourceCode.js
index cf07f0b..a03fbd6 100644
--- a/Source/devtools/front_end/UISourceCode.js
+++ b/Source/devtools/front_end/UISourceCode.js
@@ -262,14 +262,39 @@
},
/**
+ * @param {function()} callback
+ */
+ _pushCheckContentUpdatedCallback: function(callback)
+ {
+ if (!this._checkContentUpdatedCallbacks)
+ this._checkContentUpdatedCallbacks = [];
+ this._checkContentUpdatedCallbacks.push(callback);
+ },
+
+ _terminateContentCheck: function()
+ {
+ delete this._checkingContent;
+ if (this._checkContentUpdatedCallbacks) {
+ this._checkContentUpdatedCallbacks.forEach(function(callback) { callback(); });
+ delete this._checkContentUpdatedCallbacks;
+ }
+ },
+
+ /**
* @param {function()=} callback
*/
checkContentUpdated: function(callback)
{
- if (!this._project.canSetFileContent())
+ callback = callback || function() {};
+ if (!this._project.canSetFileContent()) {
+ callback();
return;
- if (this._checkingContent)
+ }
+ this._pushCheckContentUpdatedCallback(callback);
+
+ if (this._checkingContent) {
return;
+ }
this._checkingContent = true;
this._project.requestFileContent(this, contentLoaded.bind(this));
@@ -283,30 +308,22 @@
var workingCopy = this.workingCopy();
this._commitContent("", false);
this.setWorkingCopy(workingCopy);
- delete this._checkingContent;
- if (callback)
- callback();
+ this._terminateContentCheck();
return;
}
if (typeof this._lastAcceptedContent === "string" && this._lastAcceptedContent === updatedContent) {
- delete this._checkingContent;
- if (callback)
- callback();
+ this._terminateContentCheck();
return;
}
if (this._content === updatedContent) {
delete this._lastAcceptedContent;
- delete this._checkingContent;
- if (callback)
- callback();
+ this._terminateContentCheck();
return;
}
if (!this.isDirty()) {
this._commitContent(updatedContent, false);
- delete this._checkingContent;
- if (callback)
- callback();
+ this._terminateContentCheck();
return;
}
@@ -315,9 +332,7 @@
this._commitContent(updatedContent, false);
else
this._lastAcceptedContent = updatedContent;
- delete this._checkingContent;
- if (callback)
- callback();
+ this._terminateContentCheck();
}
},
@@ -770,6 +785,13 @@
{
}
+WebInspector.RawLocation.prototype = {
+ /**
+ * @return {?WebInspector.UILocation}
+ */
+ toUILocation: function() { }
+}
+
/**
* @constructor
* @param {!WebInspector.RawLocation} rawLocation
diff --git a/Source/devtools/front_end/UIUtils.js b/Source/devtools/front_end/UIUtils.js
index e8d7b02..e1a2269 100644
--- a/Source/devtools/front_end/UIUtils.js
+++ b/Source/devtools/front_end/UIUtils.js
@@ -219,7 +219,7 @@
if (event.keyIdentifier === "Up" || event.keyIdentifier === "PageUp")
direction = "Up";
else if (event.keyIdentifier === "Down" || event.keyIdentifier === "PageDown")
- direction = "Down";
+ direction = "Down";
}
return direction;
}
@@ -272,7 +272,7 @@
var direction = WebInspector._valueModificationDirection(event);
if (!direction)
return number;
-
+
var arrowKeyOrMouseWheelEvent = (event.keyIdentifier === "Up" || event.keyIdentifier === "Down" || event.type === "mousewheel");
// Jump by 10 when shift is down or jump by 0.1 when Alt/Option is down.
@@ -323,7 +323,7 @@
var originalValue = element.textContent;
var wordRange = selectionRange.startContainer.rangeOfWord(selectionRange.startOffset, WebInspector.StyleValueDelimiters, element);
var wordString = wordRange.toString();
-
+
if (suggestionHandler && suggestionHandler(wordString))
return false;
@@ -336,7 +336,7 @@
prefix = matches[1];
suffix = matches[3];
number = WebInspector._modifiedHexValue(matches[2], event);
-
+
if (customNumberHandler)
number = customNumberHandler(number);
@@ -347,11 +347,11 @@
prefix = matches[1];
suffix = matches[3];
number = WebInspector._modifiedFloatNumber(parseFloat(matches[2]), event);
-
+
// Need to check for null explicitly.
- if (number === null)
+ if (number === null)
return false;
-
+
if (customNumberHandler)
number = customNumberHandler(number);
@@ -374,7 +374,7 @@
event.handled = true;
event.preventDefault();
-
+
if (finishHandler)
finishHandler(originalValue, replacementString);
@@ -539,7 +539,7 @@
WebInspector.setCurrentFocusElement(null);
}
-WebInspector._textInputTypes = ["text", "search", "tel", "url", "email", "password"].keySet();
+WebInspector._textInputTypes = ["text", "search", "tel", "url", "email", "password"].keySet();
WebInspector._isTextEditingElement = function(element)
{
if (element instanceof HTMLInputElement)
@@ -791,12 +791,10 @@
delete WebInspector._postUpdateHandlers;
window.requestAnimationFrame(function() {
- if (WebInspector._coalescingLevel)
- return;
var keys = handlers.keys();
for (var i = 0; i < keys.length; ++i) {
var object = keys[i];
- var methods = handlers.get(object).keys();
+ var methods = handlers.get(object).items();
for (var j = 0; j < methods.length; ++j)
methods[j].call(object);
}
@@ -810,19 +808,16 @@
WebInspector.invokeOnceAfterBatchUpdate = function(object, method)
{
if (!WebInspector._coalescingLevel) {
- window.requestAnimationFrame(function() {
- if (!WebInspector._coalescingLevel)
- method.call(object);
- });
+ window.requestAnimationFrame(method.bind(object));
return;
}
var methods = WebInspector._postUpdateHandlers.get(object);
if (!methods) {
- methods = new Map();
+ methods = new Set();
WebInspector._postUpdateHandlers.put(object, methods);
}
- methods.put(method);
+ methods.add(method);
}
;(function() {
diff --git a/Source/devtools/front_end/View.js b/Source/devtools/front_end/View.js
index 628014e..f495536 100644
--- a/Source/devtools/front_end/View.js
+++ b/Source/devtools/front_end/View.js
@@ -281,8 +281,8 @@
this._cacheSize();
}
- if (this._parentView && this._hasNonZeroMinimumSize())
- this._parentView.invalidateMinimumSize();
+ if (this._parentView && this._hasNonZeroConstraints())
+ this._parentView.invalidateConstraints();
},
/**
@@ -304,8 +304,8 @@
this._visible = false;
if (this._parentIsShowing())
this._processWasHidden();
- if (this._parentView && this._hasNonZeroMinimumSize())
- this._parentView.invalidateMinimumSize();
+ if (this._parentView && this._hasNonZeroConstraints())
+ this._parentView.invalidateConstraints();
return;
}
@@ -324,8 +324,8 @@
this._parentView._children.splice(childIndex, 1);
var parent = this._parentView;
this._parentView = null;
- if (this._hasNonZeroMinimumSize())
- parent.invalidateMinimumSize();
+ if (this._hasNonZeroConstraints())
+ parent.invalidateConstraints();
} else
WebInspector.View._assert(this._isRoot, "Removing non-root view from DOM");
},
@@ -505,23 +505,35 @@
},
/**
- * @return {!Size}
+ * @return {!Constraints}
*/
- calculateMinimumSize: function()
+ calculateConstraints: function()
{
- return new Size(0, 0);
+ return new Constraints(new Size(0, 0));
},
/**
- * @return {!Size}
+ * @return {!Constraints}
*/
- minimumSize: function()
+ constraints: function()
{
- if (typeof this._minimumSize !== "undefined")
- return this._minimumSize;
- if (typeof this._cachedMinimumSize === "undefined")
- this._cachedMinimumSize = this.calculateMinimumSize();
- return this._cachedMinimumSize;
+ if (typeof this._constraints !== "undefined")
+ return this._constraints;
+ if (typeof this._cachedConstraints === "undefined")
+ this._cachedConstraints = this.calculateConstraints();
+ return this._cachedConstraints;
+ },
+
+ /**
+ * @param {number} width
+ * @param {number} height
+ * @param {number} preferredWidth
+ * @param {number} preferredHeight
+ */
+ setMinimumAndPreferredSizes: function(width, height, preferredWidth, preferredHeight)
+ {
+ this._constraints = new Constraints(new Size(width, height), new Size(preferredWidth, preferredHeight));
+ this.invalidateConstraints();
},
/**
@@ -530,26 +542,26 @@
*/
setMinimumSize: function(width, height)
{
- this._minimumSize = new Size(width, height);
- this.invalidateMinimumSize();
+ this._constraints = new Constraints(new Size(width, height));
+ this.invalidateConstraints();
},
/**
* @return {boolean}
*/
- _hasNonZeroMinimumSize: function()
+ _hasNonZeroConstraints: function()
{
- var size = this.minimumSize();
- return size.width || size.height;
+ var constraints = this.constraints();
+ return !!(constraints.minimum.width || constraints.minimum.height || constraints.preferred.width || constraints.preferred.height);
},
- invalidateMinimumSize: function()
+ invalidateConstraints: function()
{
- var cached = this._cachedMinimumSize;
- delete this._cachedMinimumSize;
- var actual = this.minimumSize();
+ var cached = this._cachedConstraints;
+ delete this._cachedConstraints;
+ var actual = this.constraints();
if (!actual.isEqual(cached) && this._parentView)
- this._parentView.invalidateMinimumSize();
+ this._parentView.invalidateConstraints();
else
this.doLayout();
},
@@ -606,12 +618,11 @@
WebInspector.VBox.prototype = {
/**
- * @return {!Size}
+ * @return {!Constraints}
*/
- calculateMinimumSize: function()
+ calculateConstraints: function()
{
- var width = 0;
- var height = 0;
+ var constraints = new Constraints(new Size(0, 0));
/**
* @this {!WebInspector.View}
@@ -619,13 +630,13 @@
*/
function updateForChild()
{
- var size = this.minimumSize();
- width = Math.max(width, size.width);
- height += size.height;
+ var child = this.constraints();
+ constraints = constraints.widthToMax(child);
+ constraints = constraints.addHeight(child);
}
this._callOnVisibleChildren(updateForChild);
- return new Size(width, height);
+ return constraints;
},
__proto__: WebInspector.View.prototype
@@ -643,12 +654,11 @@
WebInspector.HBox.prototype = {
/**
- * @return {!Size}
+ * @return {!Constraints}
*/
- calculateMinimumSize: function()
+ calculateConstraints: function()
{
- var width = 0;
- var height = 0;
+ var constraints = new Constraints(new Size(0, 0));
/**
* @this {!WebInspector.View}
@@ -656,13 +666,13 @@
*/
function updateForChild()
{
- var size = this.minimumSize();
- width += size.width;
- height = Math.max(height, size.height);
+ var child = this.constraints();
+ constraints = constraints.addWidth(child);
+ constraints = constraints.heightToMax(child);
}
this._callOnVisibleChildren(updateForChild);
- return new Size(width, height);
+ return constraints;
},
__proto__: WebInspector.View.prototype
diff --git a/Source/devtools/front_end/WatchExpressionsSidebarPane.js b/Source/devtools/front_end/WatchExpressionsSidebarPane.js
index 5cea2a4..04f0479 100644
--- a/Source/devtools/front_end/WatchExpressionsSidebarPane.js
+++ b/Source/devtools/front_end/WatchExpressionsSidebarPane.js
@@ -110,7 +110,7 @@
{
this._watchObjectGroupId = "watch-group";
- WebInspector.ObjectPropertiesSection.call(this, WebInspector.RemoteObject.fromPrimitiveValue(""));
+ WebInspector.ObjectPropertiesSection.call(this, WebInspector.runtimeModel.createRemoteObjectFromPrimitiveValue(""));
this.treeElementConstructor = WebInspector.WatchedPropertyTreeElement;
this._expandedExpressions = {};
diff --git a/Source/devtools/front_end/breakpointsList.css b/Source/devtools/front_end/breakpointsList.css
index bfcbf77..d91363c 100644
--- a/Source/devtools/front_end/breakpointsList.css
+++ b/Source/devtools/front_end/breakpointsList.css
@@ -33,7 +33,11 @@
margin-bottom: 4px;
margin-left: 23px;
margin-right: 8px;
+}
+
+.breakpoint-list .editing.being-edited {
overflow: hidden;
+ white-space: nowrap;
}
#breakpoint-condition-input {
diff --git a/Source/devtools/front_end/cm/closebrackets.js b/Source/devtools/front_end/cm/closebrackets.js
index 88718b7..6cabed6 100644
--- a/Source/devtools/front_end/cm/closebrackets.js
+++ b/Source/devtools/front_end/cm/closebrackets.js
@@ -1,8 +1,17 @@
-(function() {
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ mod(require("../../lib/codemirror"));
+ else if (typeof define == "function" && define.amd) // AMD
+ define(["../../lib/codemirror"], mod);
+ else // Plain browser env
+ mod(CodeMirror);
+})(function(CodeMirror) {
var DEFAULT_BRACKETS = "()[]{}''\"\"";
var DEFAULT_EXPLODE_ON_ENTER = "[]{}";
var SPACE_CHAR_REGEX = /\s/;
+ var Pos = CodeMirror.Pos;
+
CodeMirror.defineOption("autoCloseBrackets", false, function(cm, val, old) {
if (old != CodeMirror.Init && old)
cm.removeKeyMap("autoCloseBrackets");
@@ -19,8 +28,8 @@
});
function charsAround(cm, pos) {
- var str = cm.getRange(CodeMirror.Pos(pos.line, pos.ch - 1),
- CodeMirror.Pos(pos.line, pos.ch + 1));
+ var str = cm.getRange(Pos(pos.line, pos.ch - 1),
+ Pos(pos.line, pos.ch + 1));
return str.length == 2 ? str : null;
}
@@ -28,55 +37,103 @@
var map = {
name : "autoCloseBrackets",
Backspace: function(cm) {
- if (cm.somethingSelected()) return CodeMirror.Pass;
- var cur = cm.getCursor(), around = charsAround(cm, cur);
- if (around && pairs.indexOf(around) % 2 == 0)
- cm.replaceRange("", CodeMirror.Pos(cur.line, cur.ch - 1), CodeMirror.Pos(cur.line, cur.ch + 1));
- else
- return CodeMirror.Pass;
+ if (cm.getOption("disableInput")) return CodeMirror.Pass;
+ var ranges = cm.listSelections();
+ for (var i = 0; i < ranges.length; i++) {
+ if (!ranges[i].empty()) return CodeMirror.Pass;
+ var around = charsAround(cm, ranges[i].head);
+ if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass;
+ }
+ for (var i = ranges.length - 1; i >= 0; i--) {
+ var cur = ranges[i].head;
+ cm.replaceRange("", Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1));
+ }
}
};
var closingBrackets = "";
for (var i = 0; i < pairs.length; i += 2) (function(left, right) {
if (left != right) closingBrackets += right;
- function surround(cm) {
- var selection = cm.getSelection();
- cm.replaceSelection(left + selection + right);
- }
- function maybeOverwrite(cm) {
- var cur = cm.getCursor(), ahead = cm.getRange(cur, CodeMirror.Pos(cur.line, cur.ch + 1));
- if (ahead != right || cm.somethingSelected()) return CodeMirror.Pass;
- else cm.execCommand("goCharRight");
- }
map["'" + left + "'"] = function(cm) {
- if (left == "'" && cm.getTokenAt(cm.getCursor()).type == "comment")
- return CodeMirror.Pass;
- if (cm.somethingSelected()) return surround(cm);
- if (left == right && maybeOverwrite(cm) != CodeMirror.Pass) return;
- var cur = cm.getCursor(), ahead = CodeMirror.Pos(cur.line, cur.ch + 1);
- var line = cm.getLine(cur.line), nextChar = line.charAt(cur.ch), curChar = cur.ch > 0 ? line.charAt(cur.ch - 1) : "";
- if (left == right && CodeMirror.isWordChar(curChar))
- return CodeMirror.Pass;
- if (line.length == cur.ch || closingBrackets.indexOf(nextChar) >= 0 || SPACE_CHAR_REGEX.test(nextChar))
- cm.replaceSelection(left + right, {head: ahead, anchor: ahead});
- else
- return CodeMirror.Pass;
+ if (cm.getOption("disableInput")) return CodeMirror.Pass;
+ var ranges = cm.listSelections(), type, next;
+ for (var i = 0; i < ranges.length; i++) {
+ var range = ranges[i], cur = range.head, curType;
+ if (left == "'" && cm.getTokenTypeAt(cur) == "comment")
+ return CodeMirror.Pass;
+ var next = cm.getRange(cur, Pos(cur.line, cur.ch + 1));
+ if (!range.empty())
+ curType = "surround";
+ else if (left == right && next == right) {
+ if (cm.getRange(cur, Pos(cur.line, cur.ch + 3)) == left + left + left)
+ curType = "skipThree";
+ else
+ curType = "skip";
+ } else if (left == right && cur.ch > 1 &&
+ cm.getRange(Pos(cur.line, cur.ch - 2), cur) == left + left)
+ curType = "addFour";
+ else if (left == right && CodeMirror.isWordChar(next))
+ return CodeMirror.Pass;
+ else if (cm.getLine(cur.line).length == cur.ch || closingBrackets.indexOf(next) >= 0 || SPACE_CHAR_REGEX.test(next))
+ curType = "both";
+ else
+ return CodeMirror.Pass;
+ if (!type) type = curType;
+ else if (type != curType) return CodeMirror.Pass;
+ }
+
+ cm.operation(function() {
+ if (type == "skip") {
+ cm.execCommand("goCharRight");
+ } else if (type == "skipThree") {
+ for (var i = 0; i < 3; i++)
+ cm.execCommand("goCharRight");
+ } else if (type == "surround") {
+ var sels = cm.getSelections();
+ for (var i = 0; i < sels.length; i++)
+ sels[i] = left + sels[i] + right;
+ cm.replaceSelections(sels, "around");
+ } else if (type == "both") {
+ cm.replaceSelection(left + right, null);
+ cm.execCommand("goCharLeft");
+ } else if (type == "addFour") {
+ cm.replaceSelection(left + left + left + left, "before");
+ cm.execCommand("goCharRight");
+ }
+ });
};
- if (left != right) map["'" + right + "'"] = maybeOverwrite;
+ if (left != right) map["'" + right + "'"] = function(cm) {
+ var ranges = cm.listSelections();
+ for (var i = 0; i < ranges.length; i++) {
+ var range = ranges[i];
+ if (!range.empty() ||
+ cm.getRange(range.head, Pos(range.head.line, range.head.ch + 1)) != right)
+ return CodeMirror.Pass;
+ }
+ cm.execCommand("goCharRight");
+ };
})(pairs.charAt(i), pairs.charAt(i + 1));
return map;
}
function buildExplodeHandler(pairs) {
return function(cm) {
- var cur = cm.getCursor(), around = charsAround(cm, cur);
- if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass;
+ if (cm.getOption("disableInput")) return CodeMirror.Pass;
+ var ranges = cm.listSelections();
+ for (var i = 0; i < ranges.length; i++) {
+ if (!ranges[i].empty()) return CodeMirror.Pass;
+ var around = charsAround(cm, ranges[i].head);
+ if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass;
+ }
cm.operation(function() {
- var newPos = CodeMirror.Pos(cur.line + 1, 0);
- cm.replaceSelection("\n\n", {anchor: newPos, head: newPos}, "+input");
- cm.indentLine(cur.line + 1, null, true);
- cm.indentLine(cur.line + 2, null, true);
+ cm.replaceSelection("\n\n", null);
+ cm.execCommand("goCharLeft");
+ ranges = cm.listSelections();
+ for (var i = 0; i < ranges.length; i++) {
+ var line = ranges[i].head.line;
+ cm.indentLine(line, null, true);
+ cm.indentLine(line + 1, null, true);
+ }
});
};
}
-})();
+});
diff --git a/Source/devtools/front_end/cm/cmdevtools.css b/Source/devtools/front_end/cm/cmdevtools.css
index 838151a..68069fc 100644
--- a/Source/devtools/front_end/cm/cmdevtools.css
+++ b/Source/devtools/front_end/cm/cmdevtools.css
@@ -242,3 +242,8 @@
background-color: rgb(100%, 42%, 42%);
border: 2px solid rgb(100%, 31%, 31%);
}
+
+/** @see crbug.com/358161 */
+.CodeMirror .CodeMirror-vscrollbar, .CodeMirror .CodeMirror-hscrollbar {
+ -webkit-transform: translateZ(0);
+}
diff --git a/Source/devtools/front_end/cm/codemirror.css b/Source/devtools/front_end/cm/codemirror.css
index 23eaf74..d263e44 100644
--- a/Source/devtools/front_end/cm/codemirror.css
+++ b/Source/devtools/front_end/cm/codemirror.css
@@ -36,13 +36,14 @@
min-width: 20px;
text-align: right;
color: #999;
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
}
/* CURSOR */
.CodeMirror div.CodeMirror-cursor {
border-left: 1px solid black;
- z-index: 3;
}
/* Shown when moving in bi-directional text */
.CodeMirror div.CodeMirror-secondarycursor {
@@ -52,13 +53,17 @@
width: auto;
border: 0;
background: #7e7;
- z-index: 1;
}
/* Can style cursor different in overwrite (non-insert) mode */
-.CodeMirror div.CodeMirror-cursor.CodeMirror-overwrite {}
+div.CodeMirror-overwrite div.CodeMirror-cursor {}
.cm-tab { display: inline-block; }
+.CodeMirror-ruler {
+ border-left: 1px solid #ccc;
+ position: absolute;
+}
+
/* DEFAULT THEME */
.cm-s-default .cm-keyword {color: #708;}
@@ -114,7 +119,7 @@
/* 30px is the magic margin used to hide the element's real scrollbars */
/* See overflow: hidden in .CodeMirror */
margin-bottom: -30px; margin-right: -30px;
- padding-bottom: 30px; padding-right: 30px;
+ padding-bottom: 30px;
height: 100%;
outline: none; /* Prevent dragging from highlighting the element */
position: relative;
@@ -123,6 +128,9 @@
}
.CodeMirror-sizer {
position: relative;
+ border-right: 30px solid transparent;
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
}
/* The fake, visible scrollbars. Used to force redraw during scrolling
@@ -197,16 +205,7 @@
white-space: pre-wrap;
word-break: normal;
}
-.CodeMirror-code pre {
- border-right: 30px solid transparent;
- width: -webkit-fit-content;
- width: -moz-fit-content;
- width: fit-content;
-}
-.CodeMirror-wrap .CodeMirror-code pre {
- border-right: none;
- width: auto;
-}
+
.CodeMirror-linebackground {
position: absolute;
left: 0; right: 0; top: 0; bottom: 0;
@@ -236,11 +235,16 @@
.CodeMirror div.CodeMirror-cursor {
position: absolute;
- visibility: hidden;
border-right: none;
width: 0;
}
-.CodeMirror-focused div.CodeMirror-cursor {
+
+div.CodeMirror-cursors {
+ visibility: hidden;
+ position: relative;
+ z-index: 1;
+}
+.CodeMirror-focused div.CodeMirror-cursors {
visibility: visible;
}
@@ -255,9 +259,12 @@
/* IE7 hack to prevent it from returning funny offsetTops on the spans */
.CodeMirror span { *vertical-align: text-bottom; }
+/* Used to force a border model for a node */
+.cm-force-border { padding-right: .1px; }
+
@media print {
/* Hide the cursor when printing */
- .CodeMirror div.CodeMirror-cursor {
+ .CodeMirror div.CodeMirror-cursors {
visibility: hidden;
}
}
diff --git a/Source/devtools/front_end/cm/codemirror.js b/Source/devtools/front_end/cm/codemirror.js
index c7a5c6d..c3205cc 100644
--- a/Source/devtools/front_end/cm/codemirror.js
+++ b/Source/devtools/front_end/cm/codemirror.js
@@ -1,24 +1,36 @@
-// CodeMirror is the only global var we claim
-window.CodeMirror = (function() {
+// This is CodeMirror (http://codemirror.net), a code editor
+// implemented in JavaScript on top of the browser's DOM.
+//
+// You can find some technical background for some of the code below
+// at http://marijnhaverbeke.nl/blog/#cm-internals .
+
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ module.exports = mod();
+ else if (typeof define == "function" && define.amd) // AMD
+ return define([], mod);
+ else // Plain browser env
+ this.CodeMirror = mod();
+})(function() {
"use strict";
// BROWSER SNIFFING
- // Crude, but necessary to handle a number of hard-to-feature-detect
- // bugs and behavior differences.
+ // Kludges for bugs and behavior differences that can't be feature
+ // detected are enabled based on userAgent etc sniffing.
+
var gecko = /gecko\/\d/i.test(navigator.userAgent);
- // IE11 currently doesn't count as 'ie', since it has almost none of
- // the same bugs as earlier versions. Use ie_gt10 to handle
- // incompatibilities in that version.
- var old_ie = /MSIE \d/.test(navigator.userAgent);
- var ie_lt8 = old_ie && (document.documentMode == null || document.documentMode < 8);
- var ie_lt9 = old_ie && (document.documentMode == null || document.documentMode < 9);
- var ie_gt10 = /Trident\/([7-9]|\d{2,})\./.test(navigator.userAgent);
- var ie = old_ie || ie_gt10;
+ // ie_uptoN means Internet Explorer version N or lower
+ var ie_upto10 = /MSIE \d/.test(navigator.userAgent);
+ var ie_upto7 = ie_upto10 && (document.documentMode == null || document.documentMode < 8);
+ var ie_upto8 = ie_upto10 && (document.documentMode == null || document.documentMode < 9);
+ var ie_upto9 = ie_upto10 && (document.documentMode == null || document.documentMode < 10);
+ var ie_11up = /Trident\/([7-9]|\d{2,})\./.test(navigator.userAgent);
+ var ie = ie_upto10 || ie_11up;
var webkit = /WebKit\//.test(navigator.userAgent);
var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(navigator.userAgent);
var chrome = /Chrome\//.test(navigator.userAgent);
- var opera = /Opera\//.test(navigator.userAgent);
+ var presto = /Opera\//.test(navigator.userAgent);
var safari = /Apple Computer/.test(navigator.vendor);
var khtml = /KHTML\//.test(navigator.userAgent);
var mac_geLion = /Mac OS X 1\d\D([7-9]|\d\d)\D/.test(navigator.userAgent);
@@ -31,152 +43,182 @@
var mac = ios || /Mac/.test(navigator.platform);
var windows = /win/i.test(navigator.platform);
- var opera_version = opera && navigator.userAgent.match(/Version\/(\d*\.\d*)/);
- if (opera_version) opera_version = Number(opera_version[1]);
- if (opera_version && opera_version >= 15) { opera = false; webkit = true; }
+ var presto_version = presto && navigator.userAgent.match(/Version\/(\d*\.\d*)/);
+ if (presto_version) presto_version = Number(presto_version[1]);
+ if (presto_version && presto_version >= 15) { presto = false; webkit = true; }
// Some browsers use the wrong event properties to signal cmd/ctrl on OS X
- var flipCtrlCmd = mac && (qtwebkit || opera && (opera_version == null || opera_version < 12.11));
- var captureMiddleClick = gecko || (old_ie && !ie_lt9);
+ var flipCtrlCmd = mac && (qtwebkit || presto && (presto_version == null || presto_version < 12.11));
+ var captureRightClick = gecko || (ie && !ie_upto8);
- // Optimize some code when these features are not used
+ // Optimize some code when these features are not used.
var sawReadOnlySpans = false, sawCollapsedSpans = false;
- // CONSTRUCTOR
+ // EDITOR CONSTRUCTOR
+
+ // A CodeMirror instance represents an editor. This is the object
+ // that user code is usually dealing with.
function CodeMirror(place, options) {
if (!(this instanceof CodeMirror)) return new CodeMirror(place, options);
this.options = options = options || {};
// Determine effective options based on given values and defaults.
- for (var opt in defaults) if (!options.hasOwnProperty(opt) && defaults.hasOwnProperty(opt))
+ for (var opt in defaults) if (!options.hasOwnProperty(opt))
options[opt] = defaults[opt];
setGuttersForLineNumbers(options);
- var docStart = typeof options.value == "string" ? 0 : options.value.first;
- var display = this.display = makeDisplay(place, docStart);
+ var doc = options.value;
+ if (typeof doc == "string") doc = new Doc(doc, options.mode);
+ this.doc = doc;
+
+ var display = this.display = new Display(place, doc);
display.wrapper.CodeMirror = this;
updateGutters(this);
- if (options.autofocus && !mobile) focusInput(this);
-
- this.state = {keyMaps: [],
- overlays: [],
- modeGen: 0,
- overwrite: false, focused: false,
- suppressEdits: false,
- pasteIncoming: false, cutIncoming: false,
- draggingText: false,
- highlight: new Delayed()};
-
themeChanged(this);
if (options.lineWrapping)
this.display.wrapper.className += " CodeMirror-wrap";
+ if (options.autofocus && !mobile) focusInput(this);
- var doc = options.value;
- if (typeof doc == "string") doc = new Doc(options.value, options.mode);
- operation(this, attachDoc)(this, doc);
+ this.state = {
+ keyMaps: [], // stores maps added by addKeyMap
+ overlays: [], // highlighting overlays, as added by addOverlay
+ modeGen: 0, // bumped when mode/overlay changes, used to invalidate highlighting info
+ overwrite: false, focused: false,
+ suppressEdits: false, // used to disable editing during key handlers when in readOnly mode
+ pasteIncoming: false, cutIncoming: false, // help recognize paste/cut edits in readInput
+ draggingText: false,
+ highlight: new Delayed() // stores highlight worker timeout
+ };
// Override magic textarea content restore that IE sometimes does
// on our hidden textarea on reload
- if (old_ie) setTimeout(bind(resetInput, this, true), 20);
+ if (ie_upto10) setTimeout(bind(resetInput, this, true), 20);
registerEventHandlers(this);
- // IE throws unspecified error in certain cases, when
- // trying to access activeElement before onload
- var hasFocus; try { hasFocus = (document.activeElement == display.input); } catch(e) { }
- if (hasFocus || (options.autofocus && !mobile)) setTimeout(bind(onFocus, this), 20);
- else onBlur(this);
- operation(this, function() {
- for (var opt in optionHandlers)
- if (optionHandlers.propertyIsEnumerable(opt))
- optionHandlers[opt](this, options[opt], Init);
- for (var i = 0; i < initHooks.length; ++i) initHooks[i](this);
- })();
+ var cm = this;
+ runInOp(this, function() {
+ cm.curOp.forceUpdate = true;
+ attachDoc(cm, doc);
+
+ if ((options.autofocus && !mobile) || activeElt() == display.input)
+ setTimeout(bind(onFocus, cm), 20);
+ else
+ onBlur(cm);
+
+ for (var opt in optionHandlers) if (optionHandlers.hasOwnProperty(opt))
+ optionHandlers[opt](cm, options[opt], Init);
+ for (var i = 0; i < initHooks.length; ++i) initHooks[i](cm);
+ });
}
// DISPLAY CONSTRUCTOR
- function makeDisplay(place, docStart) {
- var d = {};
+ // The display handles the DOM integration, both for input reading
+ // and content drawing. It holds references to DOM nodes and
+ // display-related state.
- var input = d.input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none; font-size: 4px;");
+ function Display(place, doc) {
+ var d = this;
+
+ // The semihidden textarea that is focused when the editor is
+ // focused, and receives input.
+ var input = d.input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none");
+ // The textarea is kept positioned near the cursor to prevent the
+ // fact that it'll be scrolled into view on input from scrolling
+ // our fake cursor out of view. On webkit, when wrap=off, paste is
+ // very slow. So make the area wide instead.
if (webkit) input.style.width = "1000px";
else input.setAttribute("wrap", "off");
- // if border: 0; -- iOS fails to open keyboard (issue #1287)
+ // If border: 0; -- iOS fails to open keyboard (issue #1287)
if (ios) input.style.border = "1px solid black";
input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off"); input.setAttribute("spellcheck", "false");
// Wraps and hides input textarea
d.inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;");
- // The actual fake scrollbars.
- d.scrollbarH = elt("div", [elt("div", null, null, "height: 1px")], "CodeMirror-hscrollbar");
- d.scrollbarV = elt("div", [elt("div", null, null, "width: 1px")], "CodeMirror-vscrollbar");
+ // The fake scrollbar elements.
+ d.scrollbarH = elt("div", [elt("div", null, null, "height: 100%; min-height: 1px")], "CodeMirror-hscrollbar");
+ d.scrollbarV = elt("div", [elt("div", null, null, "min-width: 1px")], "CodeMirror-vscrollbar");
+ // Covers bottom-right square when both scrollbars are present.
d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler");
+ // Covers bottom of gutter when coverGutterNextToScrollbar is on
+ // and h scrollbar is present.
d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler");
- // DIVs containing the selection and the actual code
+ // Will contain the actual code, positioned to cover the viewport.
d.lineDiv = elt("div", null, "CodeMirror-code");
+ // Elements are added to these to represent selection and cursors.
d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1");
- // Blinky cursor, and element used to ensure cursor fits at the end of a line
- d.cursor = elt("div", "\u00a0", "CodeMirror-cursor");
- // Secondary cursor, shown when on a 'jump' in bi-directional text
- d.otherCursor = elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor");
- // Used to measure text size
+ d.cursorDiv = elt("div", null, "CodeMirror-cursors");
+ // A visibility: hidden element used to find the size of things.
d.measure = elt("div", null, "CodeMirror-measure");
+ // When lines outside of the viewport are measured, they are drawn in this.
+ d.lineMeasure = elt("div", null, "CodeMirror-measure");
// Wraps everything that needs to exist inside the vertically-padded coordinate system
- d.lineSpace = elt("div", [d.measure, d.selectionDiv, d.lineDiv, d.cursor, d.otherCursor],
- null, "position: relative; outline: none");
- // Moved around its parent to cover visible view
+ d.lineSpace = elt("div", [d.measure, d.lineMeasure, d.selectionDiv, d.cursorDiv, d.lineDiv],
+ null, "position: relative; outline: none");
+ // Moved around its parent to cover visible view.
d.mover = elt("div", [elt("div", [d.lineSpace], "CodeMirror-lines")], null, "position: relative");
- // Set to the height of the text, causes scrolling
+ // Set to the height of the document, allowing scrolling.
d.sizer = elt("div", [d.mover], "CodeMirror-sizer");
- // D is needed because behavior of elts with overflow: auto and padding is inconsistent across browsers
+ // Behavior of elts with overflow: auto and padding is
+ // inconsistent across browsers. This is used to ensure the
+ // scrollable area is big enough.
d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerCutOff + "px; width: 1px;");
- // Will contain the gutters, if any
+ // Will contain the gutters, if any.
d.gutters = elt("div", null, "CodeMirror-gutters");
d.lineGutter = null;
- // Provides scrolling
+ // Actual scrollable element.
d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-scroll");
d.scroller.setAttribute("tabIndex", "-1");
// The element in which the editor lives.
d.wrapper = elt("div", [d.inputDiv, d.scrollbarH, d.scrollbarV,
d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror");
- // Work around IE7 z-index bug
- if (ie_lt8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; }
- if (place.appendChild) place.appendChild(d.wrapper); else place(d.wrapper);
+ // Work around IE7 z-index bug (not perfect, hence IE7 not really being supported)
+ if (ie_upto7) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; }
// Needed to hide big blue blinking cursor on Mobile Safari
if (ios) input.style.width = "0px";
if (!webkit) d.scroller.draggable = true;
// Needed to handle Tab key in KHTML
if (khtml) { d.inputDiv.style.height = "1px"; d.inputDiv.style.position = "absolute"; }
// Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8).
- else if (ie_lt8) d.scrollbarH.style.minWidth = d.scrollbarV.style.minWidth = "18px";
+ if (ie_upto7) d.scrollbarH.style.minHeight = d.scrollbarV.style.minWidth = "18px";
- // Current visible range (may be bigger than the view window).
- d.viewOffset = d.lastSizeC = 0;
- d.showingFrom = d.showingTo = docStart;
+ if (place.appendChild) place.appendChild(d.wrapper);
+ else place(d.wrapper);
+
+ // Current rendered range (may be bigger than the view window).
+ d.viewFrom = d.viewTo = doc.first;
+ // Information about the rendered lines.
+ d.view = [];
+ // Holds info about a single rendered line when it was rendered
+ // for measurement, while not in view.
+ d.externalMeasured = null;
+ // Empty space (in pixels) above the view
+ d.viewOffset = 0;
+ d.lastSizeC = 0;
+ d.updateLineNumbers = null;
// Used to only resize the line number gutter when necessary (when
// the amount of lines crosses a boundary that makes its width change)
d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null;
// See readInput and resetInput
d.prevInput = "";
- // Set to true when a non-horizontal-scrolling widget is added. As
- // an optimization, widget aligning is skipped when d is false.
+ // Set to true when a non-horizontal-scrolling line widget is
+ // added. As an optimization, line widget aligning is skipped when
+ // this is false.
d.alignWidgets = false;
- // Flag that indicates whether we currently expect input to appear
- // (after some event like 'keypress' or 'input') and are polling
- // intensively.
+ // Flag that indicates whether we expect input to appear real soon
+ // now (after some event like 'keypress' or 'input') and are
+ // polling intensively.
d.pollingFast = false;
// Self-resetting timeout for the poller
d.poll = new Delayed();
- d.cachedCharWidth = d.cachedTextHeight = null;
- d.measureLineCache = [];
- d.measureLineCachePos = 0;
+ d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null;
// Tracks when resetInput has punted to just putting a short
- // string instead of the (large) selection.
+ // string into the textarea instead of the full selection.
d.inaccurateSelection = false;
// Tracks the maximum line length so that the horizontal scrollbar
@@ -188,7 +230,8 @@
// Used for measuring wheel scrolling granularity
d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null;
- return d;
+ // True when shift is held down.
+ d.shift = false;
}
// STATE UPDATES
@@ -217,7 +260,7 @@
cm.display.sizer.style.minWidth = "";
} else {
cm.display.wrapper.className = cm.display.wrapper.className.replace(" CodeMirror-wrap", "");
- computeMaxLength(cm);
+ findMaxLine(cm);
}
estimateLineHeights(cm);
regChange(cm);
@@ -225,16 +268,24 @@
setTimeout(function(){updateScrollbars(cm);}, 100);
}
+ // Returns a function that estimates the height of a line, to use as
+ // first approximation until the line becomes visible (and is thus
+ // properly measurable).
function estimateHeight(cm) {
var th = textHeight(cm.display), wrapping = cm.options.lineWrapping;
var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3);
return function(line) {
- if (lineIsHidden(cm.doc, line))
- return 0;
- else if (wrapping)
- return (Math.ceil(line.text.length / perLine) || 1) * th;
+ if (lineIsHidden(cm.doc, line)) return 0;
+
+ var widgetsHeight = 0;
+ if (line.widgets) for (var i = 0; i < line.widgets.length; i++) {
+ if (line.widgets[i].height) widgetsHeight += line.widgets[i].height;
+ }
+
+ if (wrapping)
+ return widgetsHeight + (Math.ceil(line.text.length / perLine) || 1) * th;
else
- return th;
+ return widgetsHeight + th;
};
}
@@ -264,6 +315,8 @@
setTimeout(function(){alignHorizontally(cm);}, 20);
}
+ // Rebuild the gutter elements, ensure the margin to the left of the
+ // code matches their width.
function updateGutters(cm) {
var gutters = cm.display.gutters, specs = cm.options.gutters;
removeChildren(gutters);
@@ -276,33 +329,40 @@
}
}
gutters.style.display = i ? "" : "none";
+ var width = gutters.offsetWidth;
+ cm.display.sizer.style.marginLeft = width + "px";
+ if (i) cm.display.scrollbarH.style.left = cm.options.fixedGutter ? width + "px" : 0;
}
- function lineLength(doc, line) {
+ // Compute the character length of a line, taking into account
+ // collapsed ranges (see markText) that might hide parts, and join
+ // other lines onto it.
+ function lineLength(line) {
if (line.height == 0) return 0;
var len = line.text.length, merged, cur = line;
while (merged = collapsedSpanAtStart(cur)) {
- var found = merged.find();
- cur = getLine(doc, found.from.line);
+ var found = merged.find(0, true);
+ cur = found.from.line;
len += found.from.ch - found.to.ch;
}
cur = line;
while (merged = collapsedSpanAtEnd(cur)) {
- var found = merged.find();
+ var found = merged.find(0, true);
len -= cur.text.length - found.from.ch;
- cur = getLine(doc, found.to.line);
+ cur = found.to.line;
len += cur.text.length - found.to.ch;
}
return len;
}
- function computeMaxLength(cm) {
+ // Find the longest line in the document.
+ function findMaxLine(cm) {
var d = cm.display, doc = cm.doc;
d.maxLine = getLine(doc, doc.first);
- d.maxLineLength = lineLength(doc, d.maxLine);
+ d.maxLineLength = lineLength(d.maxLine);
d.maxLineChanged = true;
doc.iter(function(line) {
- var len = lineLength(doc, line);
+ var len = lineLength(line);
if (len > d.maxLineLength) {
d.maxLineLength = len;
d.maxLine = line;
@@ -324,22 +384,33 @@
// SCROLLBARS
+ // Prepare DOM reads needed to update the scrollbars. Done in one
+ // shot to minimize update/measure roundtrips.
+ function measureForScrollbars(cm) {
+ var scroll = cm.display.scroller;
+ return {
+ clientHeight: scroll.clientHeight,
+ barHeight: cm.display.scrollbarV.clientHeight,
+ scrollWidth: scroll.scrollWidth, clientWidth: scroll.clientWidth,
+ barWidth: cm.display.scrollbarH.clientWidth,
+ docHeight: Math.round(cm.doc.height + paddingVert(cm.display))
+ };
+ }
+
// Re-synchronize the fake scrollbars with the actual size of the
- // content. Optionally force a scrollTop.
- function updateScrollbars(cm) {
- var d = cm.display, docHeight = cm.doc.height;
- var totalHeight = docHeight + paddingVert(d);
- d.sizer.style.minHeight = d.heightForcer.style.top = totalHeight + "px";
- d.gutters.style.height = Math.max(totalHeight, d.scroller.clientHeight - scrollerCutOff) + "px";
- var scrollHeight = Math.max(totalHeight, d.scroller.scrollHeight);
- var needsH = d.scroller.scrollWidth > (d.scroller.clientWidth + 1);
- var needsV = scrollHeight > (d.scroller.clientHeight + 1);
+ // content.
+ function updateScrollbars(cm, measure) {
+ if (!measure) measure = measureForScrollbars(cm);
+ var d = cm.display;
+ var scrollHeight = measure.docHeight + scrollerCutOff;
+ var needsH = measure.scrollWidth > measure.clientWidth;
+ var needsV = scrollHeight > measure.clientHeight;
if (needsV) {
d.scrollbarV.style.display = "block";
d.scrollbarV.style.bottom = needsH ? scrollbarWidth(d.measure) + "px" : "0";
// A bug in IE8 can cause this value to be negative, so guard it.
d.scrollbarV.firstChild.style.height =
- Math.max(0, scrollHeight - d.scroller.clientHeight + d.scrollbarV.clientHeight) + "px";
+ Math.max(0, scrollHeight - measure.clientHeight + (measure.barHeight || d.scrollbarV.clientHeight)) + "px";
} else {
d.scrollbarV.style.display = "";
d.scrollbarV.firstChild.style.height = "0";
@@ -348,7 +419,7 @@
d.scrollbarH.style.display = "block";
d.scrollbarH.style.right = needsV ? scrollbarWidth(d.measure) + "px" : "0";
d.scrollbarH.firstChild.style.width =
- (d.scroller.scrollWidth - d.scroller.clientWidth + d.scrollbarH.clientWidth) + "px";
+ (measure.scrollWidth - measure.clientWidth + (measure.barWidth || d.scrollbarH.clientWidth)) + "px";
} else {
d.scrollbarH.style.display = "";
d.scrollbarH.firstChild.style.width = "0";
@@ -365,33 +436,61 @@
if (mac_geLion && scrollbarWidth(d.measure) === 0) {
d.scrollbarV.style.minWidth = d.scrollbarH.style.minHeight = mac_geMountainLion ? "18px" : "12px";
- d.scrollbarV.style.pointerEvents = d.scrollbarH.style.pointerEvents = "none";
+ var barMouseDown = function(e) {
+ if (e_target(e) != d.scrollbarV && e_target(e) != d.scrollbarH)
+ operation(cm, onMouseDown)(e);
+ };
+ on(d.scrollbarV, "mousedown", barMouseDown);
+ on(d.scrollbarH, "mousedown", barMouseDown);
}
}
+ // Compute the lines that are visible in a given viewport (defaults
+ // the the current scroll position). viewPort may contain top,
+ // height, and ensure (see op.scrollToPos) properties.
function visibleLines(display, doc, viewPort) {
- var top = display.scroller.scrollTop, height = display.wrapper.clientHeight;
- if (typeof viewPort == "number") top = viewPort;
- else if (viewPort) {top = viewPort.top; height = viewPort.bottom - viewPort.top;}
+ var top = viewPort && viewPort.top != null ? viewPort.top : display.scroller.scrollTop;
top = Math.floor(top - paddingTop(display));
- var bottom = Math.ceil(top + height);
- return {from: lineAtHeight(doc, top), to: lineAtHeight(doc, bottom)};
+ var bottom = viewPort && viewPort.bottom != null ? viewPort.bottom : top + display.wrapper.clientHeight;
+
+ var from = lineAtHeight(doc, top), to = lineAtHeight(doc, bottom);
+ // Ensure is a {from: {line, ch}, to: {line, ch}} object, and
+ // forces those lines into the viewport (if possible).
+ if (viewPort && viewPort.ensure) {
+ var ensureFrom = viewPort.ensure.from.line, ensureTo = viewPort.ensure.to.line;
+ if (ensureFrom < from)
+ return {from: ensureFrom,
+ to: lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wrapper.clientHeight)};
+ if (Math.min(ensureTo, doc.lastLine()) >= to)
+ return {from: lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display.wrapper.clientHeight),
+ to: ensureTo};
+ }
+ return {from: from, to: to};
}
// LINE NUMBERS
+ // Re-align line numbers and gutter marks to compensate for
+ // horizontal scrolling.
function alignHorizontally(cm) {
- var display = cm.display;
+ var display = cm.display, view = display.view;
if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) return;
var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft;
- var gutterW = display.gutters.offsetWidth, l = comp + "px";
- for (var n = display.lineDiv.firstChild; n; n = n.nextSibling) if (n.alignable) {
- for (var i = 0, a = n.alignable; i < a.length; ++i) a[i].style.left = l;
+ var gutterW = display.gutters.offsetWidth, left = comp + "px";
+ for (var i = 0; i < view.length; i++) if (!view[i].hidden) {
+ if (cm.options.fixedGutter && view[i].gutter)
+ view[i].gutter.style.left = left;
+ var align = view[i].alignable;
+ if (align) for (var j = 0; j < align.length; j++)
+ align[j].style.left = left;
}
if (cm.options.fixedGutter)
display.gutters.style.left = (comp + gutterW) + "px";
}
+ // Used to ensure that the line number gutter is still the right
+ // size for the current document size. Returns true when an update
+ // is needed.
function maybeUpdateLineNumberWidth(cm) {
if (!cm.options.lineNumbers) return false;
var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display;
@@ -404,6 +503,9 @@
display.lineNumWidth = display.lineNumInnerWidth + padding;
display.lineNumChars = display.lineNumInnerWidth ? last.length : -1;
display.lineGutter.style.width = display.lineNumWidth + "px";
+ var width = display.gutters.offsetWidth;
+ display.scrollbarH.style.left = cm.options.fixedGutter ? width + "px" : 0;
+ display.sizer.style.marginLeft = width + "px";
return true;
}
return false;
@@ -412,192 +514,181 @@
function lineNumberFor(options, i) {
return String(options.lineNumberFormatter(i + options.firstLineNumber));
}
+
+ // Computes display.scroller.scrollLeft + display.gutters.offsetWidth,
+ // but using getBoundingClientRect to get a sub-pixel-accurate
+ // result.
function compensateForHScroll(display) {
- return getRect(display.scroller).left - getRect(display.sizer).left;
+ return display.scroller.getBoundingClientRect().left - display.sizer.getBoundingClientRect().left;
}
// DISPLAY DRAWING
- function updateDisplay(cm, changes, viewPort, forced) {
- var oldFrom = cm.display.showingFrom, oldTo = cm.display.showingTo, updated;
+ // Updates the display, selection, and scrollbars, using the
+ // information in display.view to find out which nodes are no longer
+ // up-to-date. Tries to bail out early when no changes are needed,
+ // unless forced is true.
+ // Returns true if an actual update happened, false otherwise.
+ function updateDisplay(cm, viewPort, forced) {
+ var oldFrom = cm.display.viewFrom, oldTo = cm.display.viewTo, updated;
var visible = visibleLines(cm.display, cm.doc, viewPort);
for (var first = true;; first = false) {
var oldWidth = cm.display.scroller.clientWidth;
- if (!updateDisplayInner(cm, changes, visible, forced)) break;
+ if (!updateDisplayInner(cm, visible, forced)) break;
updated = true;
- changes = [];
+
+ // If the max line changed since it was last measured, measure it,
+ // and ensure the document's width matches it.
+ if (cm.display.maxLineChanged && !cm.options.lineWrapping)
+ adjustContentWidth(cm);
+
+ var barMeasure = measureForScrollbars(cm);
updateSelection(cm);
- updateScrollbars(cm);
+ setDocumentHeight(cm, barMeasure);
+ updateScrollbars(cm, barMeasure);
if (first && cm.options.lineWrapping && oldWidth != cm.display.scroller.clientWidth) {
forced = true;
continue;
}
forced = false;
- // Clip forced viewport to actual scrollable area
- if (viewPort)
- viewPort = Math.min(cm.display.scroller.scrollHeight - cm.display.scroller.clientHeight,
- typeof viewPort == "number" ? viewPort : viewPort.top);
+ // Clip forced viewport to actual scrollable area.
+ if (viewPort && viewPort.top != null)
+ viewPort = {top: Math.min(barMeasure.docHeight - scrollerCutOff - barMeasure.clientHeight, viewPort.top)};
+ // Updated line heights might result in the drawn area not
+ // actually covering the viewport. Keep looping until it does.
visible = visibleLines(cm.display, cm.doc, viewPort);
- if (visible.from >= cm.display.showingFrom && visible.to <= cm.display.showingTo)
+ if (visible.from >= cm.display.viewFrom && visible.to <= cm.display.viewTo)
break;
}
+ cm.display.updateLineNumbers = null;
if (updated) {
signalLater(cm, "update", cm);
- if (cm.display.showingFrom != oldFrom || cm.display.showingTo != oldTo)
- signalLater(cm, "viewportChange", cm, cm.display.showingFrom, cm.display.showingTo);
+ if (cm.display.viewFrom != oldFrom || cm.display.viewTo != oldTo)
+ signalLater(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo);
}
return updated;
}
- // Uses a set of changes plus the current scroll position to
- // determine which DOM updates have to be made, and makes the
- // updates.
- function updateDisplayInner(cm, changes, visible, forced) {
+ // Does the actual updating of the line display. Bails out
+ // (returning false) when there is nothing to be done and forced is
+ // false.
+ function updateDisplayInner(cm, visible, forced) {
var display = cm.display, doc = cm.doc;
if (!display.wrapper.offsetWidth) {
- display.showingFrom = display.showingTo = doc.first;
- display.viewOffset = 0;
+ resetView(cm);
return;
}
// Bail out if the visible area is already rendered and nothing changed.
- if (!forced && changes.length == 0 &&
- visible.from > display.showingFrom && visible.to < display.showingTo)
+ if (!forced && visible.from >= display.viewFrom && visible.to <= display.viewTo &&
+ countDirtyView(cm) == 0)
return;
if (maybeUpdateLineNumberWidth(cm))
- changes = [{from: doc.first, to: doc.first + doc.size}];
- var gutterW = display.sizer.style.marginLeft = display.gutters.offsetWidth + "px";
- display.scrollbarH.style.left = cm.options.fixedGutter ? gutterW : "0";
+ resetView(cm);
+ var dims = getDimensions(cm);
- // Used to determine which lines need their line numbers updated
- var positionsChangedFrom = Infinity;
- if (cm.options.lineNumbers)
- for (var i = 0; i < changes.length; ++i)
- if (changes[i].diff && changes[i].from < positionsChangedFrom) { positionsChangedFrom = changes[i].from; }
-
+ // Compute a suitable new viewport (from & to)
var end = doc.first + doc.size;
var from = Math.max(visible.from - cm.options.viewportMargin, doc.first);
var to = Math.min(end, visible.to + cm.options.viewportMargin);
- if (display.showingFrom < from && from - display.showingFrom < 20) from = Math.max(doc.first, display.showingFrom);
- if (display.showingTo > to && display.showingTo - to < 20) to = Math.min(end, display.showingTo);
+ if (display.viewFrom < from && from - display.viewFrom < 20) from = Math.max(doc.first, display.viewFrom);
+ if (display.viewTo > to && display.viewTo - to < 20) to = Math.min(end, display.viewTo);
if (sawCollapsedSpans) {
- from = lineNo(visualLine(doc, getLine(doc, from)));
- while (to < end && lineIsHidden(doc, getLine(doc, to))) ++to;
+ from = visualLineNo(cm.doc, from);
+ to = visualLineEndNo(cm.doc, to);
}
- // Create a range of theoretically intact lines, and punch holes
- // in that using the change info.
- var intact = [{from: Math.max(display.showingFrom, doc.first),
- to: Math.min(display.showingTo, end)}];
- if (intact[0].from >= intact[0].to) intact = [];
- else intact = computeIntact(intact, changes);
- // When merged lines are present, we might have to reduce the
- // intact ranges because changes in continued fragments of the
- // intact lines do require the lines to be redrawn.
- if (sawCollapsedSpans)
- for (var i = 0; i < intact.length; ++i) {
- var range = intact[i], merged;
- while (merged = collapsedSpanAtEnd(getLine(doc, range.to - 1))) {
- var newTo = merged.find().from.line;
- if (newTo > range.from) range.to = newTo;
- else { intact.splice(i--, 1); break; }
- }
- }
-
- // Clip off the parts that won't be visible
- var intactLines = 0;
- for (var i = 0; i < intact.length; ++i) {
- var range = intact[i];
- if (range.from < from) range.from = from;
- if (range.to > to) range.to = to;
- if (range.from >= range.to) intact.splice(i--, 1);
- else intactLines += range.to - range.from;
- }
- if (!forced && intactLines == to - from && from == display.showingFrom && to == display.showingTo) {
- updateViewOffset(cm);
- return;
- }
- intact.sort(function(a, b) {return a.from - b.from;});
-
- // Avoid crashing on IE's "unspecified error" when in iframes
- try {
- var focused = document.activeElement;
- } catch(e) {}
- if (intactLines < (to - from) * .7) display.lineDiv.style.display = "none";
- patchDisplay(cm, from, to, intact, positionsChangedFrom);
- display.lineDiv.style.display = "";
- if (focused && document.activeElement != focused && focused.offsetHeight) focused.focus();
-
- var different = from != display.showingFrom || to != display.showingTo ||
+ var different = from != display.viewFrom || to != display.viewTo ||
display.lastSizeC != display.wrapper.clientHeight;
- // This is just a bogus formula that detects when the editor is
- // resized or the font size changes.
+ adjustView(cm, from, to);
+
+ display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom));
+ // Position the mover div to align with the current scroll position
+ cm.display.mover.style.top = display.viewOffset + "px";
+
+ var toUpdate = countDirtyView(cm);
+ if (!different && toUpdate == 0 && !forced) return;
+
+ // For big changes, we hide the enclosing element during the
+ // update, since that speeds up the operations on most browsers.
+ var focused = activeElt();
+ if (toUpdate > 4) display.lineDiv.style.display = "none";
+ patchDisplay(cm, display.updateLineNumbers, dims);
+ if (toUpdate > 4) display.lineDiv.style.display = "";
+ // There might have been a widget with a focused element that got
+ // hidden or updated, if so re-focus it.
+ if (focused && activeElt() != focused && focused.offsetHeight) focused.focus();
+
+ // Prevent selection and cursors from interfering with the scroll
+ // width.
+ removeChildren(display.cursorDiv);
+ removeChildren(display.selectionDiv);
+
if (different) {
display.lastSizeC = display.wrapper.clientHeight;
startWorker(cm, 400);
}
- display.showingFrom = from; display.showingTo = to;
- display.gutters.style.height = "";
updateHeightsInViewport(cm);
- updateViewOffset(cm);
return true;
}
+ function adjustContentWidth(cm) {
+ var display = cm.display;
+ var width = measureChar(cm, display.maxLine, display.maxLine.text.length).left;
+ display.maxLineChanged = false;
+ var minWidth = Math.max(0, width + 3);
+ var maxScrollLeft = Math.max(0, display.sizer.offsetLeft + minWidth + scrollerCutOff - display.scroller.clientWidth);
+ display.sizer.style.minWidth = minWidth + "px";
+ if (maxScrollLeft < cm.doc.scrollLeft)
+ setScrollLeft(cm, Math.min(display.scroller.scrollLeft, maxScrollLeft), true);
+ }
+
+ function setDocumentHeight(cm, measure) {
+ cm.display.sizer.style.minHeight = cm.display.heightForcer.style.top = measure.docHeight + "px";
+ cm.display.gutters.style.height = Math.max(measure.docHeight, measure.clientHeight - scrollerCutOff) + "px";
+ }
+
+ // Read the actual heights of the rendered lines, and update their
+ // stored heights to match.
function updateHeightsInViewport(cm) {
var display = cm.display;
var prevBottom = display.lineDiv.offsetTop;
- for (var node = display.lineDiv.firstChild, height; node; node = node.nextSibling) if (node.lineObj) {
- if (ie_lt8) {
- var bot = node.offsetTop + node.offsetHeight;
+ for (var i = 0; i < display.view.length; i++) {
+ var cur = display.view[i], height;
+ if (cur.hidden) continue;
+ if (ie_upto7) {
+ var bot = cur.node.offsetTop + cur.node.offsetHeight;
height = bot - prevBottom;
prevBottom = bot;
} else {
- var box = getRect(node);
+ var box = cur.node.getBoundingClientRect();
height = box.bottom - box.top;
}
- var diff = node.lineObj.height - height;
+ var diff = cur.line.height - height;
if (height < 2) height = textHeight(display);
if (diff > .001 || diff < -.001) {
- updateLineHeight(node.lineObj, height);
- var widgets = node.lineObj.widgets;
- if (widgets) for (var i = 0; i < widgets.length; ++i)
- widgets[i].height = widgets[i].node.offsetHeight;
+ updateLineHeight(cur.line, height);
+ updateWidgetHeight(cur.line);
+ if (cur.rest) for (var j = 0; j < cur.rest.length; j++)
+ updateWidgetHeight(cur.rest[j]);
}
}
}
- function updateViewOffset(cm) {
- var off = cm.display.viewOffset = heightAtLine(cm, getLine(cm.doc, cm.display.showingFrom));
- // Position the mover div to align with the current virtual scroll position
- cm.display.mover.style.top = off + "px";
+ // Read and store the height of line widgets associated with the
+ // given line.
+ function updateWidgetHeight(line) {
+ if (line.widgets) for (var i = 0; i < line.widgets.length; ++i)
+ line.widgets[i].height = line.widgets[i].node.offsetHeight;
}
- function computeIntact(intact, changes) {
- for (var i = 0, l = changes.length || 0; i < l; ++i) {
- var change = changes[i], intact2 = [], diff = change.diff || 0;
- for (var j = 0, l2 = intact.length; j < l2; ++j) {
- var range = intact[j];
- if (change.to <= range.from && change.diff) {
- intact2.push({from: range.from + diff, to: range.to + diff});
- } else if (change.to <= range.from || change.from >= range.to) {
- intact2.push(range);
- } else {
- if (change.from > range.from)
- intact2.push({from: range.from, to: change.from});
- if (change.to < range.to)
- intact2.push({from: change.to + diff, to: range.to + diff});
- }
- }
- intact = intact2;
- }
- return intact;
- }
-
+ // Do a bulk-read of the DOM positions and sizes needed to draw the
+ // view, so that we don't interleave reading and writing to the DOM.
function getDimensions(cm) {
var d = cm.display, left = {}, width = {};
for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) {
@@ -611,154 +702,207 @@
wrapperWidth: d.wrapper.clientWidth};
}
- function patchDisplay(cm, from, to, intact, updateNumbersFrom) {
- var dims = getDimensions(cm);
+ // Sync the actual display DOM structure with display.view, removing
+ // nodes for lines that are no longer in view, and creating the ones
+ // that are not there yet, and updating the ones that are out of
+ // date.
+ function patchDisplay(cm, updateNumbersFrom, dims) {
var display = cm.display, lineNumbers = cm.options.lineNumbers;
- if (!intact.length && (!webkit || !cm.display.currentWheelTarget))
- removeChildren(display.lineDiv);
var container = display.lineDiv, cur = container.firstChild;
function rm(node) {
var next = node.nextSibling;
- if (webkit && mac && cm.display.currentWheelTarget == node) {
+ // Works around a throw-scroll bug in OS X Webkit
+ if (webkit && mac && cm.display.currentWheelTarget == node)
node.style.display = "none";
- node.lineObj = null;
- } else {
+ else
node.parentNode.removeChild(node);
- }
return next;
}
- var nextIntact = intact.shift(), lineN = from;
- cm.doc.iter(from, to, function(line) {
- if (nextIntact && nextIntact.to == lineN) nextIntact = intact.shift();
- if (lineIsHidden(cm.doc, line)) {
- if (line.height != 0) updateLineHeight(line, 0);
- if (line.widgets && cur && cur.previousSibling) for (var i = 0; i < line.widgets.length; ++i) {
- var w = line.widgets[i];
- if (w.showIfHidden) {
- var prev = cur.previousSibling;
- if (/pre/i.test(prev.nodeName)) {
- var wrap = elt("div", null, null, "position: relative");
- prev.parentNode.replaceChild(wrap, prev);
- wrap.appendChild(prev);
- prev = wrap;
- }
- var wnode = prev.appendChild(elt("div", [w.node], "CodeMirror-linewidget"));
- if (!w.handleMouseEvents) wnode.ignoreEvents = true;
- positionLineWidget(w, wnode, prev, dims);
- }
+ var view = display.view, lineN = display.viewFrom;
+ // Loop over the elements in the view, syncing cur (the DOM nodes
+ // in display.lineDiv) with the view as we go.
+ for (var i = 0; i < view.length; i++) {
+ var lineView = view[i];
+ if (lineView.hidden) {
+ } else if (!lineView.node) { // Not drawn yet
+ var node = buildLineElement(cm, lineView, lineN, dims);
+ container.insertBefore(node, cur);
+ } else { // Already drawn
+ while (cur != lineView.node) cur = rm(cur);
+ var updateNumber = lineNumbers && updateNumbersFrom != null &&
+ updateNumbersFrom <= lineN && lineView.lineNumber;
+ if (lineView.changes) {
+ if (indexOf(lineView.changes, "gutter") > -1) updateNumber = false;
+ updateLineForChanges(cm, lineView, lineN, dims);
}
- } else if (nextIntact && nextIntact.from <= lineN && nextIntact.to > lineN) {
- // This line is intact. Skip to the actual node. Update its
- // line number if needed.
- while (cur.lineObj != line) cur = rm(cur);
- if (lineNumbers && updateNumbersFrom <= lineN && cur.lineNumber)
- setTextContent(cur.lineNumber, lineNumberFor(cm.options, lineN));
- cur = cur.nextSibling;
- } else {
- // For lines with widgets, make an attempt to find and reuse
- // the existing element, so that widgets aren't needlessly
- // removed and re-inserted into the dom
- if (line.widgets) for (var j = 0, search = cur, reuse; search && j < 20; ++j, search = search.nextSibling)
- if (search.lineObj == line && /div/i.test(search.nodeName)) { reuse = search; break; }
- // This line needs to be generated.
- var lineNode = buildLineElement(cm, line, lineN, dims, reuse);
- if (lineNode != reuse) {
- container.insertBefore(lineNode, cur);
- } else {
- while (cur != reuse) cur = rm(cur);
- cur = cur.nextSibling;
+ if (updateNumber) {
+ removeChildren(lineView.lineNumber);
+ lineView.lineNumber.appendChild(document.createTextNode(lineNumberFor(cm.options, lineN)));
}
-
- lineNode.lineObj = line;
+ cur = lineView.node.nextSibling;
}
- ++lineN;
- });
+ lineN += lineView.size;
+ }
while (cur) cur = rm(cur);
}
- function buildLineElement(cm, line, lineNo, dims, reuse) {
- var built = buildLineContent(cm, line), lineElement = built.pre;
- var markers = line.gutterMarkers, display = cm.display, wrap;
-
- var bgClass = built.bgClass ? built.bgClass + " " + (line.bgClass || "") : line.bgClass;
- if (!cm.options.lineNumbers && !markers && !bgClass && !line.wrapClass && !line.widgets)
- return lineElement;
-
- // Lines with gutter elements, widgets or a background class need
- // to be wrapped again, and have the extra elements added to the
- // wrapper div
-
- if (reuse) {
- reuse.alignable = null;
- var isOk = true, widgetsSeen = 0, insertBefore = null;
- for (var n = reuse.firstChild, next; n; n = next) {
- next = n.nextSibling;
- if (!/\bCodeMirror-linewidget\b/.test(n.className)) {
- reuse.removeChild(n);
- } else {
- for (var i = 0; i < line.widgets.length; ++i) {
- var widget = line.widgets[i];
- if (widget.node == n.firstChild) {
- if (!widget.above && !insertBefore) insertBefore = n;
- positionLineWidget(widget, n, reuse, dims);
- ++widgetsSeen;
- break;
- }
- }
- if (i == line.widgets.length) { isOk = false; break; }
- }
- }
- reuse.insertBefore(lineElement, insertBefore);
- if (isOk && widgetsSeen == line.widgets.length) {
- wrap = reuse;
- reuse.className = line.wrapClass || "";
- }
+ // When an aspect of a line changes, a string is added to
+ // lineView.changes. This updates the relevant part of the line's
+ // DOM structure.
+ function updateLineForChanges(cm, lineView, lineN, dims) {
+ for (var j = 0; j < lineView.changes.length; j++) {
+ var type = lineView.changes[j];
+ if (type == "text") updateLineText(cm, lineView);
+ else if (type == "gutter") updateLineGutter(cm, lineView, lineN, dims);
+ else if (type == "class") updateLineClasses(lineView);
+ else if (type == "widget") updateLineWidgets(lineView, dims);
}
- if (!wrap) {
- wrap = elt("div", null, line.wrapClass, "position: relative");
- wrap.appendChild(lineElement);
+ lineView.changes = null;
+ }
+
+ // Lines with gutter elements, widgets or a background class need to
+ // be wrapped, and have the extra elements added to the wrapper div
+ function ensureLineWrapped(lineView) {
+ if (lineView.node == lineView.text) {
+ lineView.node = elt("div", null, null, "position: relative");
+ if (lineView.text.parentNode)
+ lineView.text.parentNode.replaceChild(lineView.node, lineView.text);
+ lineView.node.appendChild(lineView.text);
+ if (ie_upto7) lineView.node.style.zIndex = 2;
}
- // Kludge to make sure the styled element lies behind the selection (by z-index)
- if (bgClass)
- wrap.insertBefore(elt("div", null, bgClass + " CodeMirror-linebackground"), wrap.firstChild);
+ return lineView.node;
+ }
+
+ function updateLineBackground(lineView) {
+ var cls = lineView.bgClass ? lineView.bgClass + " " + (lineView.line.bgClass || "") : lineView.line.bgClass;
+ if (cls) cls += " CodeMirror-linebackground";
+ if (lineView.background) {
+ if (cls) lineView.background.className = cls;
+ else { lineView.background.parentNode.removeChild(lineView.background); lineView.background = null; }
+ } else if (cls) {
+ var wrap = ensureLineWrapped(lineView);
+ lineView.background = wrap.insertBefore(elt("div", null, cls), wrap.firstChild);
+ }
+ }
+
+ // Wrapper around buildLineContent which will reuse the structure
+ // in display.externalMeasured when possible.
+ function getLineContent(cm, lineView) {
+ var ext = cm.display.externalMeasured;
+ if (ext && ext.line == lineView.line) {
+ cm.display.externalMeasured = null;
+ lineView.measure = ext.measure;
+ return ext.built;
+ }
+ return buildLineContent(cm, lineView);
+ }
+
+ // Redraw the line's text. Interacts with the background and text
+ // classes because the mode may output tokens that influence these
+ // classes.
+ function updateLineText(cm, lineView) {
+ var cls = lineView.text.className;
+ var built = getLineContent(cm, lineView);
+ if (lineView.text == lineView.node) lineView.node = built.pre;
+ lineView.text.parentNode.replaceChild(built.pre, lineView.text);
+ lineView.text = built.pre;
+ if (built.bgClass != lineView.bgClass || built.textClass != lineView.textClass) {
+ lineView.bgClass = built.bgClass;
+ lineView.textClass = built.textClass;
+ updateLineClasses(lineView);
+ } else if (cls) {
+ lineView.text.className = cls;
+ }
+ }
+
+ function updateLineClasses(lineView) {
+ updateLineBackground(lineView);
+ if (lineView.line.wrapClass)
+ ensureLineWrapped(lineView).className = lineView.line.wrapClass;
+ else if (lineView.node != lineView.text)
+ lineView.node.className = "";
+ var textClass = lineView.textClass ? lineView.textClass + " " + (lineView.line.textClass || "") : lineView.line.textClass;
+ lineView.text.className = textClass || "";
+ }
+
+ function updateLineGutter(cm, lineView, lineN, dims) {
+ if (lineView.gutter) {
+ lineView.node.removeChild(lineView.gutter);
+ lineView.gutter = null;
+ }
+ var markers = lineView.line.gutterMarkers;
if (cm.options.lineNumbers || markers) {
- var gutterWrap = wrap.insertBefore(elt("div", null, "CodeMirror-gutter-wrapper", "position: absolute; left: " +
- (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px"),
- lineElement);
- if (cm.options.fixedGutter) (wrap.alignable || (wrap.alignable = [])).push(gutterWrap);
+ var wrap = ensureLineWrapped(lineView);
+ var gutterWrap = lineView.gutter =
+ wrap.insertBefore(elt("div", null, "CodeMirror-gutter-wrapper", "position: absolute; left: " +
+ (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px"),
+ lineView.text);
if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"]))
- wrap.lineNumber = gutterWrap.appendChild(
- elt("div", lineNumberFor(cm.options, lineNo),
+ lineView.lineNumber = gutterWrap.appendChild(
+ elt("div", lineNumberFor(cm.options, lineN),
"CodeMirror-linenumber CodeMirror-gutter-elt",
"left: " + dims.gutterLeft["CodeMirror-linenumbers"] + "px; width: "
- + display.lineNumInnerWidth + "px"));
- if (markers)
- for (var k = 0; k < cm.options.gutters.length; ++k) {
- var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id];
- if (found)
- gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", "left: " +
- dims.gutterLeft[id] + "px; width: " + dims.gutterWidth[id] + "px"));
- }
+ + cm.display.lineNumInnerWidth + "px"));
+ if (markers) for (var k = 0; k < cm.options.gutters.length; ++k) {
+ var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id];
+ if (found)
+ gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", "left: " +
+ dims.gutterLeft[id] + "px; width: " + dims.gutterWidth[id] + "px"));
+ }
}
- if (ie_lt8) wrap.style.zIndex = 2;
- if (line.widgets && wrap != reuse) for (var i = 0, ws = line.widgets; i < ws.length; ++i) {
+ }
+
+ function updateLineWidgets(lineView, dims) {
+ if (lineView.alignable) lineView.alignable = null;
+ for (var node = lineView.node.firstChild, next; node; node = next) {
+ var next = node.nextSibling;
+ if (node.className == "CodeMirror-linewidget")
+ lineView.node.removeChild(node);
+ }
+ insertLineWidgets(lineView, dims);
+ }
+
+ // Build a line's DOM representation from scratch
+ function buildLineElement(cm, lineView, lineN, dims) {
+ var built = getLineContent(cm, lineView);
+ lineView.text = lineView.node = built.pre;
+ if (built.bgClass) lineView.bgClass = built.bgClass;
+ if (built.textClass) lineView.textClass = built.textClass;
+
+ updateLineClasses(lineView);
+ updateLineGutter(cm, lineView, lineN, dims);
+ insertLineWidgets(lineView, dims);
+ return lineView.node;
+ }
+
+ // A lineView may contain multiple logical lines (when merged by
+ // collapsed spans). The widgets for all of them need to be drawn.
+ function insertLineWidgets(lineView, dims) {
+ insertLineWidgetsFor(lineView.line, lineView, dims, true);
+ if (lineView.rest) for (var i = 0; i < lineView.rest.length; i++)
+ insertLineWidgetsFor(lineView.rest[i], lineView, dims, false);
+ }
+
+ function insertLineWidgetsFor(line, lineView, dims, allowAbove) {
+ if (!line.widgets) return;
+ var wrap = ensureLineWrapped(lineView);
+ for (var i = 0, ws = line.widgets; i < ws.length; ++i) {
var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget");
if (!widget.handleMouseEvents) node.ignoreEvents = true;
- positionLineWidget(widget, node, wrap, dims);
- if (widget.above)
- wrap.insertBefore(node, cm.options.lineNumbers && line.height != 0 ? gutterWrap : lineElement);
+ positionLineWidget(widget, node, lineView, dims);
+ if (allowAbove && widget.above)
+ wrap.insertBefore(node, lineView.gutter || lineView.text);
else
wrap.appendChild(node);
signalLater(widget, "redraw");
}
- return wrap;
}
- function positionLineWidget(widget, node, wrap, dims) {
+ function positionLineWidget(widget, node, lineView, dims) {
if (widget.noHScroll) {
- (wrap.alignable || (wrap.alignable = [])).push(node);
+ (lineView.alignable || (lineView.alignable = [])).push(node);
var width = dims.wrapperWidth;
node.style.left = dims.fixedPos + "px";
if (!widget.coverGutter) {
@@ -774,57 +918,367 @@
}
}
+ // POSITION OBJECT
+
+ // A Pos instance represents a position within the text.
+ var Pos = CodeMirror.Pos = function(line, ch) {
+ if (!(this instanceof Pos)) return new Pos(line, ch);
+ this.line = line; this.ch = ch;
+ };
+
+ // Compare two positions, return 0 if they are the same, a negative
+ // number when a is less, and a positive number otherwise.
+ var cmp = CodeMirror.cmpPos = function(a, b) { return a.line - b.line || a.ch - b.ch; };
+
+ function copyPos(x) {return Pos(x.line, x.ch);}
+ function maxPos(a, b) { return cmp(a, b) < 0 ? b : a; }
+ function minPos(a, b) { return cmp(a, b) < 0 ? a : b; }
+
// SELECTION / CURSOR
- function updateSelection(cm) {
- var display = cm.display;
- var collapsed = posEq(cm.doc.sel.from, cm.doc.sel.to);
- if (collapsed || cm.options.showCursorWhenSelecting)
- updateSelectionCursor(cm);
- else
- display.cursor.style.display = display.otherCursor.style.display = "none";
- if (!collapsed)
- updateSelectionRange(cm);
- else
- display.selectionDiv.style.display = "none";
+ // Selection objects are immutable. A new one is created every time
+ // the selection changes. A selection is one or more non-overlapping
+ // (and non-touching) ranges, sorted, and an integer that indicates
+ // which one is the primary selection (the one that's scrolled into
+ // view, that getCursor returns, etc).
+ function Selection(ranges, primIndex) {
+ this.ranges = ranges;
+ this.primIndex = primIndex;
+ }
- // Move the hidden textarea near the cursor to prevent scrolling artifacts
- if (cm.options.moveInputWithCursor) {
- var headPos = cursorCoords(cm, cm.doc.sel.head, "div");
- var wrapOff = getRect(display.wrapper), lineOff = getRect(display.lineDiv);
- display.inputDiv.style.top = Math.max(0, Math.min(display.wrapper.clientHeight - 10,
- headPos.top + lineOff.top - wrapOff.top)) + "px";
- display.inputDiv.style.left = Math.max(0, Math.min(display.wrapper.clientWidth - 10,
- headPos.left + lineOff.left - wrapOff.left)) + "px";
+ Selection.prototype = {
+ primary: function() { return this.ranges[this.primIndex]; },
+ equals: function(other) {
+ if (other == this) return true;
+ if (other.primIndex != this.primIndex || other.ranges.length != this.ranges.length) return false;
+ for (var i = 0; i < this.ranges.length; i++) {
+ var here = this.ranges[i], there = other.ranges[i];
+ if (cmp(here.anchor, there.anchor) != 0 || cmp(here.head, there.head) != 0) return false;
+ }
+ return true;
+ },
+ deepCopy: function() {
+ for (var out = [], i = 0; i < this.ranges.length; i++)
+ out[i] = new Range(copyPos(this.ranges[i].anchor), copyPos(this.ranges[i].head));
+ return new Selection(out, this.primIndex);
+ },
+ somethingSelected: function() {
+ for (var i = 0; i < this.ranges.length; i++)
+ if (!this.ranges[i].empty()) return true;
+ return false;
+ },
+ contains: function(pos, end) {
+ if (!end) end = pos;
+ for (var i = 0; i < this.ranges.length; i++) {
+ var range = this.ranges[i];
+ if (cmp(end, range.from()) >= 0 && cmp(pos, range.to()) <= 0)
+ return i;
+ }
+ return -1;
+ }
+ };
+
+ function Range(anchor, head) {
+ this.anchor = anchor; this.head = head;
+ }
+
+ Range.prototype = {
+ from: function() { return minPos(this.anchor, this.head); },
+ to: function() { return maxPos(this.anchor, this.head); },
+ empty: function() {
+ return this.head.line == this.anchor.line && this.head.ch == this.anchor.ch;
+ }
+ };
+
+ // Take an unsorted, potentially overlapping set of ranges, and
+ // build a selection out of it. 'Consumes' ranges array (modifying
+ // it).
+ function normalizeSelection(ranges, primIndex) {
+ var prim = ranges[primIndex];
+ ranges.sort(function(a, b) { return cmp(a.from(), b.from()); });
+ primIndex = indexOf(ranges, prim);
+ for (var i = 1; i < ranges.length; i++) {
+ var cur = ranges[i], prev = ranges[i - 1];
+ if (cmp(prev.to(), cur.from()) >= 0) {
+ var from = minPos(prev.from(), cur.from()), to = maxPos(prev.to(), cur.to());
+ var inv = prev.empty() ? cur.from() == cur.head : prev.from() == prev.head;
+ if (i <= primIndex) --primIndex;
+ ranges.splice(--i, 2, new Range(inv ? to : from, inv ? from : to));
+ }
+ }
+ return new Selection(ranges, primIndex);
+ }
+
+ function simpleSelection(anchor, head) {
+ return new Selection([new Range(anchor, head || anchor)], 0);
+ }
+
+ // Most of the external API clips given positions to make sure they
+ // actually exist within the document.
+ function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1));}
+ function clipPos(doc, pos) {
+ if (pos.line < doc.first) return Pos(doc.first, 0);
+ var last = doc.first + doc.size - 1;
+ if (pos.line > last) return Pos(last, getLine(doc, last).text.length);
+ return clipToLen(pos, getLine(doc, pos.line).text.length);
+ }
+ function clipToLen(pos, linelen) {
+ var ch = pos.ch;
+ if (ch == null || ch > linelen) return Pos(pos.line, linelen);
+ else if (ch < 0) return Pos(pos.line, 0);
+ else return pos;
+ }
+ function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size;}
+ function clipPosArray(doc, array) {
+ for (var out = [], i = 0; i < array.length; i++) out[i] = clipPos(doc, array[i]);
+ return out;
+ }
+
+ // SELECTION UPDATES
+
+ // The 'scroll' parameter given to many of these indicated whether
+ // the new cursor position should be scrolled into view after
+ // modifying the selection.
+
+ // If shift is held or the extend flag is set, extends a range to
+ // include a given position (and optionally a second position).
+ // Otherwise, simply returns the range between the given positions.
+ // Used for cursor motion and such.
+ function extendRange(doc, range, head, other) {
+ if (doc.cm && doc.cm.display.shift || doc.extend) {
+ var anchor = range.anchor;
+ if (other) {
+ var posBefore = cmp(head, anchor) < 0;
+ if (posBefore != (cmp(other, anchor) < 0)) {
+ anchor = head;
+ head = other;
+ } else if (posBefore != (cmp(head, other) < 0)) {
+ head = other;
+ }
+ }
+ return new Range(anchor, head);
+ } else {
+ return new Range(other || head, head);
}
}
- // No selection, plain cursor
- function updateSelectionCursor(cm) {
- var display = cm.display, pos = cursorCoords(cm, cm.doc.sel.head, "div");
- display.cursor.style.left = pos.left + "px";
- display.cursor.style.top = pos.top + "px";
- display.cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px";
- display.cursor.style.display = "";
-
- if (pos.other) {
- display.otherCursor.style.display = "";
- display.otherCursor.style.left = pos.other.left + "px";
- display.otherCursor.style.top = pos.other.top + "px";
- display.otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px";
- } else { display.otherCursor.style.display = "none"; }
+ // Extend the primary selection range, discard the rest.
+ function extendSelection(doc, head, other, options) {
+ setSelection(doc, new Selection([extendRange(doc, doc.sel.primary(), head, other)], 0), options);
}
- // Highlight selection
- function updateSelectionRange(cm) {
- var display = cm.display, doc = cm.doc, sel = cm.doc.sel;
+ // Extend all selections (pos is an array of selections with length
+ // equal the number of selections)
+ function extendSelections(doc, heads, options) {
+ for (var out = [], i = 0; i < doc.sel.ranges.length; i++)
+ out[i] = extendRange(doc, doc.sel.ranges[i], heads[i], null);
+ var newSel = normalizeSelection(out, doc.sel.primIndex);
+ setSelection(doc, newSel, options);
+ }
+
+ // Updates a single range in the selection.
+ function replaceOneSelection(doc, i, range, options) {
+ var ranges = doc.sel.ranges.slice(0);
+ ranges[i] = range;
+ setSelection(doc, normalizeSelection(ranges, doc.sel.primIndex), options);
+ }
+
+ // Reset the selection to a single range.
+ function setSimpleSelection(doc, anchor, head, options) {
+ setSelection(doc, simpleSelection(anchor, head), options);
+ }
+
+ // Give beforeSelectionChange handlers a change to influence a
+ // selection update.
+ function filterSelectionChange(doc, sel) {
+ var obj = {
+ ranges: sel.ranges,
+ update: function(ranges) {
+ this.ranges = [];
+ for (var i = 0; i < ranges.length; i++)
+ this.ranges[i] = new Range(clipPos(doc, ranges[i].anchor),
+ clipPos(doc, ranges[i].head));
+ }
+ };
+ signal(doc, "beforeSelectionChange", doc, obj);
+ if (doc.cm) signal(doc.cm, "beforeSelectionChange", doc.cm, obj);
+ if (obj.ranges != sel.ranges) return normalizeSelection(obj.ranges, obj.ranges.length - 1);
+ else return sel;
+ }
+
+ function setSelectionReplaceHistory(doc, sel, options) {
+ var done = doc.history.done, last = lst(done);
+ if (last && last.ranges) {
+ done[done.length - 1] = sel;
+ setSelectionNoUndo(doc, sel, options);
+ } else {
+ setSelection(doc, sel, options);
+ }
+ }
+
+ // Set a new selection.
+ function setSelection(doc, sel, options) {
+ setSelectionNoUndo(doc, sel, options);
+ addSelectionToHistory(doc, doc.sel, doc.cm ? doc.cm.curOp.id : NaN, options);
+ }
+
+ function setSelectionNoUndo(doc, sel, options) {
+ if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange"))
+ sel = filterSelectionChange(doc, sel);
+
+ var bias = cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1;
+ setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true));
+
+ if (!(options && options.scroll === false) && doc.cm)
+ ensureCursorVisible(doc.cm);
+ }
+
+ function setSelectionInner(doc, sel) {
+ if (sel.equals(doc.sel)) return;
+
+ doc.sel = sel;
+
+ if (doc.cm)
+ doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged =
+ doc.cm.curOp.cursorActivity = true;
+ signalLater(doc, "cursorActivity", doc);
+ }
+
+ // Verify that the selection does not partially select any atomic
+ // marked ranges.
+ function reCheckSelection(doc) {
+ setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false), sel_dontScroll);
+ }
+
+ // Return a selection that does not partially select any atomic
+ // ranges.
+ function skipAtomicInSelection(doc, sel, bias, mayClear) {
+ var out;
+ for (var i = 0; i < sel.ranges.length; i++) {
+ var range = sel.ranges[i];
+ var newAnchor = skipAtomic(doc, range.anchor, bias, mayClear);
+ var newHead = skipAtomic(doc, range.head, bias, mayClear);
+ if (out || newAnchor != range.anchor || newHead != range.head) {
+ if (!out) out = sel.ranges.slice(0, i);
+ out[i] = new Range(newAnchor, newHead);
+ }
+ }
+ return out ? normalizeSelection(out, sel.primIndex) : sel;
+ }
+
+ // Ensure a given position is not inside an atomic range.
+ function skipAtomic(doc, pos, bias, mayClear) {
+ var flipped = false, curPos = pos;
+ var dir = bias || 1;
+ doc.cantEdit = false;
+ search: for (;;) {
+ var line = getLine(doc, curPos.line);
+ if (line.markedSpans) {
+ for (var i = 0; i < line.markedSpans.length; ++i) {
+ var sp = line.markedSpans[i], m = sp.marker;
+ if ((sp.from == null || (m.inclusiveLeft ? sp.from <= curPos.ch : sp.from < curPos.ch)) &&
+ (sp.to == null || (m.inclusiveRight ? sp.to >= curPos.ch : sp.to > curPos.ch))) {
+ if (mayClear) {
+ signal(m, "beforeCursorEnter");
+ if (m.explicitlyCleared) {
+ if (!line.markedSpans) break;
+ else {--i; continue;}
+ }
+ }
+ if (!m.atomic) continue;
+ var newPos = m.find(dir < 0 ? -1 : 1);
+ if (cmp(newPos, curPos) == 0) {
+ newPos.ch += dir;
+ if (newPos.ch < 0) {
+ if (newPos.line > doc.first) newPos = clipPos(doc, Pos(newPos.line - 1));
+ else newPos = null;
+ } else if (newPos.ch > line.text.length) {
+ if (newPos.line < doc.first + doc.size - 1) newPos = Pos(newPos.line + 1, 0);
+ else newPos = null;
+ }
+ if (!newPos) {
+ if (flipped) {
+ // Driven in a corner -- no valid cursor position found at all
+ // -- try again *with* clearing, if we didn't already
+ if (!mayClear) return skipAtomic(doc, pos, bias, true);
+ // Otherwise, turn off editing until further notice, and return the start of the doc
+ doc.cantEdit = true;
+ return Pos(doc.first, 0);
+ }
+ flipped = true; newPos = pos; dir = -dir;
+ }
+ }
+ curPos = newPos;
+ continue search;
+ }
+ }
+ }
+ return curPos;
+ }
+ }
+
+ // SELECTION DRAWING
+
+ // Redraw the selection and/or cursor
+ function updateSelection(cm) {
+ var display = cm.display, doc = cm.doc;
+ var curFragment = document.createDocumentFragment();
+ var selFragment = document.createDocumentFragment();
+
+ for (var i = 0; i < doc.sel.ranges.length; i++) {
+ var range = doc.sel.ranges[i];
+ var collapsed = range.empty();
+ if (collapsed || cm.options.showCursorWhenSelecting)
+ updateSelectionCursor(cm, range, curFragment);
+ if (!collapsed)
+ updateSelectionRange(cm, range, selFragment);
+ }
+
+ // Move the hidden textarea near the cursor to prevent scrolling artifacts
+ if (cm.options.moveInputWithCursor) {
+ var headPos = cursorCoords(cm, doc.sel.primary().head, "div");
+ var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect();
+ var top = Math.max(0, Math.min(display.wrapper.clientHeight - 10,
+ headPos.top + lineOff.top - wrapOff.top));
+ var left = Math.max(0, Math.min(display.wrapper.clientWidth - 10,
+ headPos.left + lineOff.left - wrapOff.left));
+ display.inputDiv.style.top = top + "px";
+ display.inputDiv.style.left = left + "px";
+ }
+
+ removeChildrenAndAdd(display.cursorDiv, curFragment);
+ removeChildrenAndAdd(display.selectionDiv, selFragment);
+ }
+
+ // Draws a cursor for the given range
+ function updateSelectionCursor(cm, range, output) {
+ var pos = cursorCoords(cm, range.head, "div");
+
+ var cursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor"));
+ cursor.style.left = pos.left + "px";
+ cursor.style.top = pos.top + "px";
+ cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px";
+
+ if (pos.other) {
+ // Secondary cursor, shown when on a 'jump' in bi-directional text
+ var otherCursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor"));
+ otherCursor.style.display = "";
+ otherCursor.style.left = pos.other.left + "px";
+ otherCursor.style.top = pos.other.top + "px";
+ otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px";
+ }
+ }
+
+ // Draws the given range as a highlighted selection
+ function updateSelectionRange(cm, range, output) {
+ var display = cm.display, doc = cm.doc;
var fragment = document.createDocumentFragment();
- var clientWidth = display.lineSpace.offsetWidth, pl = paddingLeft(cm.display);
+ var padding = paddingH(cm.display), leftSide = padding.left, rightSide = display.lineSpace.offsetWidth - padding.right;
function add(left, top, width, bottom) {
if (top < 0) top = 0;
fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left +
- "px; top: " + top + "px; width: " + (width == null ? clientWidth - left : width) +
+ "px; top: " + top + "px; width: " + (width == null ? rightSide - left : width) +
"px; height: " + (bottom - top) + "px"));
}
@@ -847,44 +1301,44 @@
left = leftPos.left;
right = rightPos.right;
}
- if (fromArg == null && from == 0) left = pl;
+ if (fromArg == null && from == 0) left = leftSide;
if (rightPos.top - leftPos.top > 3) { // Different lines, draw top part
add(left, leftPos.top, null, leftPos.bottom);
- left = pl;
+ left = leftSide;
if (leftPos.bottom < rightPos.top) add(left, leftPos.bottom, null, rightPos.top);
}
- if (toArg == null && to == lineLen) right = clientWidth;
+ if (toArg == null && to == lineLen) right = rightSide;
if (!start || leftPos.top < start.top || leftPos.top == start.top && leftPos.left < start.left)
start = leftPos;
if (!end || rightPos.bottom > end.bottom || rightPos.bottom == end.bottom && rightPos.right > end.right)
end = rightPos;
- if (left < pl + 1) left = pl;
+ if (left < leftSide + 1) left = leftSide;
add(left, rightPos.top, right - left, rightPos.bottom);
});
return {start: start, end: end};
}
- if (sel.from.line == sel.to.line) {
- drawForLine(sel.from.line, sel.from.ch, sel.to.ch);
+ var sFrom = range.from(), sTo = range.to();
+ if (sFrom.line == sTo.line) {
+ drawForLine(sFrom.line, sFrom.ch, sTo.ch);
} else {
- var fromLine = getLine(doc, sel.from.line), toLine = getLine(doc, sel.to.line);
- var singleVLine = visualLine(doc, fromLine) == visualLine(doc, toLine);
- var leftEnd = drawForLine(sel.from.line, sel.from.ch, singleVLine ? fromLine.text.length : null).end;
- var rightStart = drawForLine(sel.to.line, singleVLine ? 0 : null, sel.to.ch).start;
+ var fromLine = getLine(doc, sFrom.line), toLine = getLine(doc, sTo.line);
+ var singleVLine = visualLine(fromLine) == visualLine(toLine);
+ var leftEnd = drawForLine(sFrom.line, sFrom.ch, singleVLine ? fromLine.text.length + 1 : null).end;
+ var rightStart = drawForLine(sTo.line, singleVLine ? 0 : null, sTo.ch).start;
if (singleVLine) {
if (leftEnd.top < rightStart.top - 2) {
add(leftEnd.right, leftEnd.top, null, leftEnd.bottom);
- add(pl, rightStart.top, rightStart.left, rightStart.bottom);
+ add(leftSide, rightStart.top, rightStart.left, rightStart.bottom);
} else {
add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom);
}
}
if (leftEnd.bottom < rightStart.top)
- add(pl, leftEnd.bottom, null, rightStart.top);
+ add(leftSide, leftEnd.bottom, null, rightStart.top);
}
- removeChildrenAndAdd(display.selectionDiv, fragment);
- display.selectionDiv.style.display = "";
+ output.appendChild(fragment);
}
// Cursor-blinking
@@ -893,37 +1347,35 @@
var display = cm.display;
clearInterval(display.blinker);
var on = true;
- display.cursor.style.visibility = display.otherCursor.style.visibility = "";
+ display.cursorDiv.style.visibility = "";
if (cm.options.cursorBlinkRate > 0)
display.blinker = setInterval(function() {
- display.cursor.style.visibility = display.otherCursor.style.visibility = (on = !on) ? "" : "hidden";
+ display.cursorDiv.style.visibility = (on = !on) ? "" : "hidden";
}, cm.options.cursorBlinkRate);
}
// HIGHLIGHT WORKER
function startWorker(cm, time) {
- if (cm.doc.mode.startState && cm.doc.frontier < cm.display.showingTo)
+ if (cm.doc.mode.startState && cm.doc.frontier < cm.display.viewTo)
cm.state.highlight.set(time, bind(highlightWorker, cm));
}
function highlightWorker(cm) {
var doc = cm.doc;
if (doc.frontier < doc.first) doc.frontier = doc.first;
- if (doc.frontier >= cm.display.showingTo) return;
+ if (doc.frontier >= cm.display.viewTo) return;
var end = +new Date + cm.options.workTime;
var state = copyState(doc.mode, getStateBefore(cm, doc.frontier));
- var changed = [], prevChange;
- doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.showingTo + 500), function(line) {
- if (doc.frontier >= cm.display.showingFrom) { // Visible
+
+ runInOp(cm, function() {
+ doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.viewTo + 500), function(line) {
+ if (doc.frontier >= cm.display.viewFrom) { // Visible
var oldStyles = line.styles;
line.styles = highlightLine(cm, line, state, true);
var ischange = !oldStyles || oldStyles.length != line.styles.length;
for (var i = 0; !ischange && i < oldStyles.length; ++i) ischange = oldStyles[i] != line.styles[i];
- if (ischange) {
- if (prevChange && prevChange.end == doc.frontier) prevChange.end++;
- else changed.push(prevChange = {start: doc.frontier, end: doc.frontier + 1});
- }
+ if (ischange) regLineChange(cm, doc.frontier, "text");
line.stateAfter = copyState(doc.mode, state);
} else {
processLine(cm, line.text, state);
@@ -935,11 +1387,7 @@
return true;
}
});
- if (changed.length)
- operation(cm, function() {
- for (var i = 0; i < changed.length; ++i)
- regChange(this, changed[i].start, changed[i].end);
- })();
+ });
}
// Finds the line to start with when starting a parse. Tries to
@@ -971,7 +1419,7 @@
else state = copyState(doc.mode, state);
doc.iter(pos, n, function(line) {
processLine(cm, line.text, state);
- var save = pos == n - 1 || pos % 5 == 0 || pos >= display.showingFrom && pos < display.showingTo;
+ var save = pos == n - 1 || pos % 5 == 0 || pos >= display.viewFrom && pos < display.viewTo;
line.stateAfter = save ? copyState(doc.mode, state) : null;
++pos;
});
@@ -983,183 +1431,222 @@
function paddingTop(display) {return display.lineSpace.offsetTop;}
function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight;}
- function paddingLeft(display) {
- var e = removeChildrenAndAdd(display.measure, elt("pre", null, null, "text-align: left")).appendChild(elt("span", "x"));
- return e.offsetLeft;
+ function paddingH(display) {
+ if (display.cachedPaddingH) return display.cachedPaddingH;
+ var e = removeChildrenAndAdd(display.measure, elt("pre", "x"));
+ var style = window.getComputedStyle ? window.getComputedStyle(e) : e.currentStyle;
+ return display.cachedPaddingH = {left: parseInt(style.paddingLeft),
+ right: parseInt(style.paddingRight)};
}
- function measureChar(cm, line, ch, data, bias) {
- var dir = -1;
- data = data || measureLine(cm, line);
- if (data.crude) {
- var left = data.left + ch * data.width;
- return {left: left, right: left + data.width, top: data.top, bottom: data.bottom};
- }
-
- for (var pos = ch;; pos += dir) {
- var r = data[pos];
- if (r) break;
- if (dir < 0 && pos == 0) dir = 1;
- }
- bias = pos > ch ? "left" : pos < ch ? "right" : bias;
- if (bias == "left" && r.leftSide) r = r.leftSide;
- else if (bias == "right" && r.rightSide) r = r.rightSide;
- return {left: pos < ch ? r.right : r.left,
- right: pos > ch ? r.left : r.right,
- top: r.top,
- bottom: r.bottom};
- }
-
- function findCachedMeasurement(cm, line) {
- var cache = cm.display.measureLineCache;
- for (var i = 0; i < cache.length; ++i) {
- var memo = cache[i];
- if (memo.text == line.text && memo.markedSpans == line.markedSpans &&
- cm.display.scroller.clientWidth == memo.width &&
- memo.classes == line.textClass + "|" + line.wrapClass)
- return memo;
- }
- }
-
- function clearCachedMeasurement(cm, line) {
- var exists = findCachedMeasurement(cm, line);
- if (exists) exists.text = exists.measure = exists.markedSpans = null;
- }
-
- function measureLine(cm, line) {
- // First look in the cache
- var cached = findCachedMeasurement(cm, line);
- if (cached) return cached.measure;
-
- // Failing that, recompute and store result in cache
- var measure = measureLineInner(cm, line);
- var cache = cm.display.measureLineCache;
- var memo = {text: line.text, width: cm.display.scroller.clientWidth,
- markedSpans: line.markedSpans, measure: measure,
- classes: line.textClass + "|" + line.wrapClass};
- if (cache.length == 16) cache[++cm.display.measureLineCachePos % 16] = memo;
- else cache.push(memo);
- return measure;
- }
-
- function measureLineInner(cm, line) {
- if (!cm.options.lineWrapping && line.text.length >= cm.options.crudeMeasuringFrom)
- return crudelyMeasureLine(cm, line);
-
- var display = cm.display, measure = emptyArray(line.text.length);
- var pre = buildLineContent(cm, line, measure, true).pre;
-
- // IE does not cache element positions of inline elements between
- // calls to getBoundingClientRect. This makes the loop below,
- // which gathers the positions of all the characters on the line,
- // do an amount of layout work quadratic to the number of
- // characters. When line wrapping is off, we try to improve things
- // by first subdividing the line into a bunch of inline blocks, so
- // that IE can reuse most of the layout information from caches
- // for those blocks. This does interfere with line wrapping, so it
- // doesn't work when wrapping is on, but in that case the
- // situation is slightly better, since IE does cache line-wrapping
- // information and only recomputes per-line.
- if (old_ie && !ie_lt8 && !cm.options.lineWrapping && pre.childNodes.length > 100) {
- var fragment = document.createDocumentFragment();
- var chunk = 10, n = pre.childNodes.length;
- for (var i = 0, chunks = Math.ceil(n / chunk); i < chunks; ++i) {
- var wrap = elt("div", null, null, "display: inline-block");
- for (var j = 0; j < chunk && n; ++j) {
- wrap.appendChild(pre.firstChild);
- --n;
- }
- fragment.appendChild(wrap);
- }
- pre.appendChild(fragment);
- }
-
- removeChildrenAndAdd(display.measure, pre);
-
- var outer = getRect(display.lineDiv);
- var vranges = [], data = emptyArray(line.text.length), maxBot = pre.offsetHeight;
- // Work around an IE7/8 bug where it will sometimes have randomly
- // replaced our pre with a clone at this point.
- if (ie_lt9 && display.measure.first != pre)
- removeChildrenAndAdd(display.measure, pre);
-
- function measureRect(rect) {
- var top = rect.top - outer.top, bot = rect.bottom - outer.top;
- if (bot > maxBot) bot = maxBot;
- if (top < 0) top = 0;
- for (var i = vranges.length - 2; i >= 0; i -= 2) {
- var rtop = vranges[i], rbot = vranges[i+1];
- if (rtop > bot || rbot < top) continue;
- if (rtop <= top && rbot >= bot ||
- top <= rtop && bot >= rbot ||
- Math.min(bot, rbot) - Math.max(top, rtop) >= (bot - top) >> 1) {
- vranges[i] = Math.min(top, rtop);
- vranges[i+1] = Math.max(bot, rbot);
- break;
+ // Ensure the lineView.wrapping.heights array is populated. This is
+ // an array of bottom offsets for the lines that make up a drawn
+ // line. When lineWrapping is on, there might be more than one
+ // height.
+ function ensureLineHeights(cm, lineView, rect) {
+ var wrapping = cm.options.lineWrapping;
+ var curWidth = wrapping && cm.display.scroller.clientWidth;
+ if (!lineView.measure.heights || wrapping && lineView.measure.width != curWidth) {
+ var heights = lineView.measure.heights = [];
+ if (wrapping) {
+ lineView.measure.width = curWidth;
+ var rects = lineView.text.firstChild.getClientRects();
+ for (var i = 0; i < rects.length - 1; i++) {
+ var cur = rects[i], next = rects[i + 1];
+ if (Math.abs(cur.bottom - next.bottom) > 2)
+ heights.push((cur.bottom + next.top) / 2 - rect.top);
}
}
- if (i < 0) { i = vranges.length; vranges.push(top, bot); }
- return {left: rect.left - outer.left,
- right: rect.right - outer.left,
- top: i, bottom: null};
+ heights.push(rect.bottom - rect.top);
}
- function finishRect(rect) {
- rect.bottom = vranges[rect.top+1];
- rect.top = vranges[rect.top];
- }
+ }
- for (var i = 0, cur; i < measure.length; ++i) if (cur = measure[i]) {
- var node = cur, rect = null;
- // A widget might wrap, needs special care
- if (/\bCodeMirror-widget\b/.test(cur.className) && cur.getClientRects) {
- if (cur.firstChild.nodeType == 1) node = cur.firstChild;
- var rects = node.getClientRects();
- if (rects.length > 1) {
- rect = data[i] = measureRect(rects[0]);
- rect.rightSide = measureRect(rects[rects.length - 1]);
- }
+ // Find a line map (mapping character offsets to text nodes) and a
+ // measurement cache for the given line number. (A line view might
+ // contain multiple lines when collapsed ranges are present.)
+ function mapFromLineView(lineView, line, lineN) {
+ if (lineView.line == line)
+ return {map: lineView.measure.map, cache: lineView.measure.cache};
+ for (var i = 0; i < lineView.rest.length; i++)
+ if (lineView.rest[i] == line)
+ return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i]};
+ for (var i = 0; i < lineView.rest.length; i++)
+ if (lineNo(lineView.rest[i]) > lineN)
+ return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i], before: true};
+ }
+
+ // Render a line into the hidden node display.externalMeasured. Used
+ // when measurement is needed for a line that's not in the viewport.
+ function updateExternalMeasurement(cm, line) {
+ line = visualLine(line);
+ var lineN = lineNo(line);
+ var view = cm.display.externalMeasured = new LineView(cm.doc, line, lineN);
+ view.lineN = lineN;
+ var built = view.built = buildLineContent(cm, view);
+ view.text = built.pre;
+ removeChildrenAndAdd(cm.display.lineMeasure, built.pre);
+ return view;
+ }
+
+ // Get a {top, bottom, left, right} box (in line-local coordinates)
+ // for a given character.
+ function measureChar(cm, line, ch, bias) {
+ return measureCharPrepared(cm, prepareMeasureForLine(cm, line), ch, bias);
+ }
+
+ // Find a line view that corresponds to the given line number.
+ function findViewForLine(cm, lineN) {
+ if (lineN >= cm.display.viewFrom && lineN < cm.display.viewTo)
+ return cm.display.view[findViewIndex(cm, lineN)];
+ var ext = cm.display.externalMeasured;
+ if (ext && lineN >= ext.lineN && lineN < ext.lineN + ext.size)
+ return ext;
+ }
+
+ // Measurement can be split in two steps, the set-up work that
+ // applies to the whole line, and the measurement of the actual
+ // character. Functions like coordsChar, that need to do a lot of
+ // measurements in a row, can thus ensure that the set-up work is
+ // only done once.
+ function prepareMeasureForLine(cm, line) {
+ var lineN = lineNo(line);
+ var view = findViewForLine(cm, lineN);
+ if (view && !view.text)
+ view = null;
+ else if (view && view.changes)
+ updateLineForChanges(cm, view, lineN, getDimensions(cm));
+ if (!view)
+ view = updateExternalMeasurement(cm, line);
+
+ var info = mapFromLineView(view, line, lineN);
+ return {
+ line: line, view: view, rect: null,
+ map: info.map, cache: info.cache, before: info.before,
+ hasHeights: false
+ };
+ }
+
+ // Given a prepared measurement object, measures the position of an
+ // actual character (or fetches it from the cache).
+ function measureCharPrepared(cm, prepared, ch, bias) {
+ if (prepared.before) ch = -1;
+ var key = ch + (bias || ""), found;
+ if (prepared.cache.hasOwnProperty(key)) {
+ found = prepared.cache[key];
+ } else {
+ if (!prepared.rect)
+ prepared.rect = prepared.view.text.getBoundingClientRect();
+ if (!prepared.hasHeights) {
+ ensureLineHeights(cm, prepared.view, prepared.rect);
+ prepared.hasHeights = true;
}
- if (!rect) rect = data[i] = measureRect(getRect(node));
- if (cur.measureRight) rect.right = getRect(cur.measureRight).left - outer.left;
- if (cur.leftSide) rect.leftSide = measureRect(getRect(cur.leftSide));
+ found = measureCharInner(cm, prepared, ch, bias);
+ if (!found.bogus) prepared.cache[key] = found;
}
- removeChildren(cm.display.measure);
- for (var i = 0, cur; i < data.length; ++i) if (cur = data[i]) {
- finishRect(cur);
- if (cur.leftSide) finishRect(cur.leftSide);
- if (cur.rightSide) finishRect(cur.rightSide);
- }
- return data;
+ return {left: found.left, right: found.right, top: found.top, bottom: found.bottom};
}
- function crudelyMeasureLine(cm, line) {
- var copy = new Line(line.text.slice(0, 100), null);
- if (line.textClass) copy.textClass = line.textClass;
- var measure = measureLineInner(cm, copy);
- var left = measureChar(cm, copy, 0, measure, "left");
- var right = measureChar(cm, copy, 99, measure, "right");
- return {crude: true, top: left.top, left: left.left, bottom: left.bottom, width: (right.right - left.left) / 100};
+ var nullRect = {left: 0, right: 0, top: 0, bottom: 0};
+
+ function measureCharInner(cm, prepared, ch, bias) {
+ var map = prepared.map;
+
+ var node, start, end, collapse;
+ // First, search the line map for the text node corresponding to,
+ // or closest to, the target character.
+ for (var i = 0; i < map.length; i += 3) {
+ var mStart = map[i], mEnd = map[i + 1];
+ if (ch < mStart) {
+ start = 0; end = 1;
+ collapse = "left";
+ } else if (ch < mEnd) {
+ start = ch - mStart;
+ end = start + 1;
+ } else if (i == map.length - 3 || ch == mEnd && map[i + 3] > ch) {
+ end = mEnd - mStart;
+ start = end - 1;
+ if (ch >= mEnd) collapse = "right";
+ }
+ if (start != null) {
+ node = map[i + 2];
+ if (mStart == mEnd && bias == (node.insertLeft ? "left" : "right"))
+ collapse = bias;
+ if (bias == "left" && start == 0)
+ while (i && map[i - 2] == map[i - 3] && map[i - 1].insertLeft) {
+ node = map[(i -= 3) + 2];
+ collapse = "left";
+ }
+ if (bias == "right" && start == mEnd - mStart)
+ while (i < map.length - 3 && map[i + 3] == map[i + 4] && !map[i + 5].insertLeft) {
+ node = map[(i += 3) + 2];
+ collapse = "right";
+ }
+ break;
+ }
+ }
+
+ var rect;
+ if (node.nodeType == 3) { // If it is a text node, use a range to retrieve the coordinates.
+ while (start && isExtendingChar(prepared.line.text.charAt(mStart + start))) --start;
+ while (mStart + end < mEnd && isExtendingChar(prepared.line.text.charAt(mStart + end))) ++end;
+ if (ie_upto8 && start == 0 && end == mEnd - mStart) {
+ rect = node.parentNode.getBoundingClientRect();
+ } else if (ie && cm.options.lineWrapping) {
+ var rects = range(node, start, end).getClientRects();
+ if (rects.length)
+ rect = rects[bias == "right" ? rects.length - 1 : 0];
+ else
+ rect = nullRect;
+ } else {
+ rect = range(node, start, end).getBoundingClientRect();
+ }
+ } else { // If it is a widget, simply get the box for the whole widget.
+ if (start > 0) collapse = bias = "right";
+ var rects;
+ if (cm.options.lineWrapping && (rects = node.getClientRects()).length > 1)
+ rect = rects[bias == "right" ? rects.length - 1 : 0];
+ else
+ rect = node.getBoundingClientRect();
+ }
+ if (ie_upto8 && !start && (!rect || !rect.left && !rect.right)) {
+ var rSpan = node.parentNode.getClientRects()[0];
+ if (rSpan)
+ rect = {left: rSpan.left, right: rSpan.left + charWidth(cm.display), top: rSpan.top, bottom: rSpan.bottom};
+ else
+ rect = nullRect;
+ }
+
+ var top, bot = (rect.bottom + rect.top) / 2 - prepared.rect.top;
+ var heights = prepared.view.measure.heights;
+ for (var i = 0; i < heights.length - 1; i++)
+ if (bot < heights[i]) break;
+ top = i ? heights[i - 1] : 0; bot = heights[i];
+ var result = {left: (collapse == "right" ? rect.right : rect.left) - prepared.rect.left,
+ right: (collapse == "left" ? rect.left : rect.right) - prepared.rect.left,
+ top: top, bottom: bot};
+ if (!rect.left && !rect.right) result.bogus = true;
+ return result;
}
- function measureLineWidth(cm, line) {
- var hasBadSpan = false;
- if (line.markedSpans) for (var i = 0; i < line.markedSpans; ++i) {
- var sp = line.markedSpans[i];
- if (sp.collapsed && (sp.to == null || sp.to == line.text.length)) hasBadSpan = true;
+ function clearLineMeasurementCacheFor(lineView) {
+ if (lineView.measure) {
+ lineView.measure.cache = {};
+ lineView.measure.heights = null;
+ if (lineView.rest) for (var i = 0; i < lineView.rest.length; i++)
+ lineView.measure.caches[i] = {};
}
- var cached = !hasBadSpan && findCachedMeasurement(cm, line);
- if (cached || line.text.length >= cm.options.crudeMeasuringFrom)
- return measureChar(cm, line, line.text.length, cached && cached.measure, "right").right;
+ }
- var pre = buildLineContent(cm, line, null, true).pre;
- var end = pre.appendChild(zeroWidthElement(cm.display.measure));
- removeChildrenAndAdd(cm.display.measure, pre);
- return getRect(end).right - getRect(cm.display.lineDiv).left;
+ function clearLineMeasurementCache(cm) {
+ cm.display.externalMeasure = null;
+ removeChildren(cm.display.lineMeasure);
+ for (var i = 0; i < cm.display.view.length; i++)
+ clearLineMeasurementCacheFor(cm.display.view[i]);
}
function clearCaches(cm) {
- cm.display.measureLineCache.length = cm.display.measureLineCachePos = 0;
- cm.display.cachedCharWidth = cm.display.cachedTextHeight = null;
+ clearLineMeasurementCache(cm);
+ cm.display.cachedCharWidth = cm.display.cachedTextHeight = cm.display.cachedPaddingH = null;
if (!cm.options.lineWrapping) cm.display.maxLineChanged = true;
cm.display.lineNumChars = null;
}
@@ -1167,7 +1654,9 @@
function pageScrollX() { return window.pageXOffset || (document.documentElement || document.body).scrollLeft; }
function pageScrollY() { return window.pageYOffset || (document.documentElement || document.body).scrollTop; }
- // Context is one of "line", "div" (display.lineDiv), "local"/null (editor), or "page"
+ // Converts a {top, bottom, left, right} box from line-local
+ // coordinates into another coordinate system. Context may be one of
+ // "line", "div" (display.lineDiv), "local"/null (editor), or "page".
function intoCoordSystem(cm, lineObj, rect, context) {
if (lineObj.widgets) for (var i = 0; i < lineObj.widgets.length; ++i) if (lineObj.widgets[i].above) {
var size = widgetHeight(lineObj.widgets[i]);
@@ -1175,11 +1664,11 @@
}
if (context == "line") return rect;
if (!context) context = "local";
- var yOff = heightAtLine(cm, lineObj);
+ var yOff = heightAtLine(lineObj);
if (context == "local") yOff += paddingTop(cm.display);
else yOff -= cm.display.viewOffset;
if (context == "page" || context == "window") {
- var lOff = getRect(cm.display.lineSpace);
+ var lOff = cm.display.lineSpace.getBoundingClientRect();
yOff += lOff.top + (context == "window" ? 0 : pageScrollY());
var xOff = lOff.left + (context == "window" ? 0 : pageScrollX());
rect.left += xOff; rect.right += xOff;
@@ -1188,8 +1677,8 @@
return rect;
}
- // Context may be "window", "page", "div", or "local"/null
- // Result is in "div" coords
+ // Coverts a box from "div" coords to another coordinate system.
+ // Context may be "window", "page", "div", or "local"/null.
function fromCoordSystem(cm, coords, context) {
if (context == "div") return coords;
var left = coords.left, top = coords.top;
@@ -1198,25 +1687,28 @@
left -= pageScrollX();
top -= pageScrollY();
} else if (context == "local" || !context) {
- var localBox = getRect(cm.display.sizer);
+ var localBox = cm.display.sizer.getBoundingClientRect();
left += localBox.left;
top += localBox.top;
}
- var lineSpaceBox = getRect(cm.display.lineSpace);
+ var lineSpaceBox = cm.display.lineSpace.getBoundingClientRect();
return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top};
}
function charCoords(cm, pos, context, lineObj, bias) {
if (!lineObj) lineObj = getLine(cm.doc, pos.line);
- return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, null, bias), context);
+ return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, bias), context);
}
- function cursorCoords(cm, pos, context, lineObj, measurement) {
+ // Returns a box for a given cursor position, which may have an
+ // 'other' property containing the position of the secondary cursor
+ // on a bidi boundary.
+ function cursorCoords(cm, pos, context, lineObj, preparedMeasure) {
lineObj = lineObj || getLine(cm.doc, pos.line);
- if (!measurement) measurement = measureLine(cm, lineObj);
+ if (!preparedMeasure) preparedMeasure = prepareMeasureForLine(cm, lineObj);
function get(ch, right) {
- var m = measureChar(cm, lineObj, ch, measurement, right ? "right" : "left");
+ var m = measureCharPrepared(cm, preparedMeasure, ch, right ? "right" : "left");
if (right) m.left = m.right; else m.right = m.left;
return intoCoordSystem(cm, lineObj, m, context);
}
@@ -1242,43 +1734,59 @@
return val;
}
+ // Used to cheaply estimate the coordinates for a position. Used for
+ // intermediate scroll updates.
+ function estimateCoords(cm, pos) {
+ var left = 0, pos = clipPos(cm.doc, pos);
+ if (!cm.options.lineWrapping) left = charWidth(cm.display) * pos.ch;
+ var lineObj = getLine(cm.doc, pos.line);
+ var top = heightAtLine(lineObj) + paddingTop(cm.display);
+ return {left: left, right: left, top: top, bottom: top + lineObj.height};
+ }
+
+ // Positions returned by coordsChar contain some extra information.
+ // xRel is the relative x position of the input coordinates compared
+ // to the found position (so xRel > 0 means the coordinates are to
+ // the right of the character position, for example). When outside
+ // is true, that means the coordinates lie outside the line's
+ // vertical range.
function PosWithInfo(line, ch, outside, xRel) {
- var pos = new Pos(line, ch);
+ var pos = Pos(line, ch);
pos.xRel = xRel;
if (outside) pos.outside = true;
return pos;
}
- // Coords must be lineSpace-local
+ // Compute the character position closest to the given coordinates.
+ // Input must be lineSpace-local ("div" coordinate system).
function coordsChar(cm, x, y) {
var doc = cm.doc;
y += cm.display.viewOffset;
if (y < 0) return PosWithInfo(doc.first, 0, true, -1);
- var lineNo = lineAtHeight(doc, y), last = doc.first + doc.size - 1;
- if (lineNo > last)
+ var lineN = lineAtHeight(doc, y), last = doc.first + doc.size - 1;
+ if (lineN > last)
return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, true, 1);
if (x < 0) x = 0;
+ var lineObj = getLine(doc, lineN);
for (;;) {
- var lineObj = getLine(doc, lineNo);
- var found = coordsCharInner(cm, lineObj, lineNo, x, y);
+ var found = coordsCharInner(cm, lineObj, lineN, x, y);
var merged = collapsedSpanAtEnd(lineObj);
- var mergedPos = merged && merged.find();
+ var mergedPos = merged && merged.find(0, true);
if (merged && (found.ch > mergedPos.from.ch || found.ch == mergedPos.from.ch && found.xRel > 0))
- lineNo = mergedPos.to.line;
+ lineN = lineNo(lineObj = mergedPos.to.line);
else
return found;
}
}
function coordsCharInner(cm, lineObj, lineNo, x, y) {
- var innerOff = y - heightAtLine(cm, lineObj);
+ var innerOff = y - heightAtLine(lineObj);
var wrongLine = false, adjust = 2 * cm.display.wrapper.clientWidth;
- var measurement = measureLine(cm, lineObj);
+ var preparedMeasure = prepareMeasureForLine(cm, lineObj);
function getX(ch) {
- var sp = cursorCoords(cm, Pos(lineNo, ch), "line",
- lineObj, measurement);
+ var sp = cursorCoords(cm, Pos(lineNo, ch), "line", lineObj, preparedMeasure);
wrongLine = true;
if (innerOff > sp.bottom) return sp.left - adjust;
else if (innerOff < sp.top) return sp.left + adjust;
@@ -1298,7 +1806,7 @@
var xDiff = x - (ch == from ? fromX : toX);
while (isExtendingChar(lineObj.text.charAt(ch))) ++ch;
var pos = PosWithInfo(lineNo, ch, ch == from ? fromOutside : toOutside,
- xDiff < 0 ? -1 : xDiff ? 1 : 0);
+ xDiff < -1 ? -1 : xDiff > 1 ? 1 : 0);
return pos;
}
var step = Math.ceil(dist / 2), middle = from + step;
@@ -1313,6 +1821,7 @@
}
var measureText;
+ // Compute the default text height.
function textHeight(display) {
if (display.cachedTextHeight != null) return display.cachedTextHeight;
if (measureText == null) {
@@ -1332,84 +1841,88 @@
return height || 1;
}
+ // Compute the default character width.
function charWidth(display) {
if (display.cachedCharWidth != null) return display.cachedCharWidth;
- var anchor = elt("span", "x");
+ var anchor = elt("span", "xxxxxxxxxx");
var pre = elt("pre", [anchor]);
removeChildrenAndAdd(display.measure, pre);
- var width = anchor.offsetWidth;
+ var rect = anchor.getBoundingClientRect(), width = (rect.right - rect.left) / 10;
if (width > 2) display.cachedCharWidth = width;
return width || 10;
}
// OPERATIONS
- // Operations are used to wrap changes in such a way that each
- // change won't have to update the cursor and display (which would
- // be awkward, slow, and error-prone), but instead updates are
- // batched and then all combined and executed at once.
+ // Operations are used to wrap a series of changes to the editor
+ // state in such a way that each change won't have to update the
+ // cursor and display (which would be awkward, slow, and
+ // error-prone). Instead, display updates are batched and then all
+ // combined and executed at once.
var nextOpId = 0;
+ // Start a new operation.
function startOperation(cm) {
cm.curOp = {
- // An array of ranges of lines that have to be updated. See
- // updateDisplay.
- changes: [],
- forceUpdate: false,
- updateInput: null,
- userSelChange: null,
- textChanged: null,
- selectionChanged: false,
- cursorActivity: false,
- updateMaxLine: false,
- updateScrollPos: false,
- id: ++nextOpId
+ viewChanged: false, // Flag that indicates that lines might need to be redrawn
+ startHeight: cm.doc.height, // Used to detect need to update scrollbar
+ forceUpdate: false, // Used to force a redraw
+ updateInput: null, // Whether to reset the input textarea
+ typing: false, // Whether this reset should be careful to leave existing text (for compositing)
+ changeObjs: null, // Accumulated changes, for firing change events
+ cursorActivity: false, // Whether to fire a cursorActivity event
+ selectionChanged: false, // Whether the selection needs to be redrawn
+ updateMaxLine: false, // Set when the widest line needs to be determined anew
+ scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pushed to DOM yet
+ scrollToPos: null, // Used to scroll to a specific position
+ id: ++nextOpId // Unique ID
};
if (!delayedCallbackDepth++) delayedCallbacks = [];
}
+ // Finish an operation, updating the display and signalling delayed events
function endOperation(cm) {
var op = cm.curOp, doc = cm.doc, display = cm.display;
cm.curOp = null;
- if (op.updateMaxLine) computeMaxLength(cm);
- if (display.maxLineChanged && !cm.options.lineWrapping && display.maxLine) {
- var width = measureLineWidth(cm, display.maxLine);
- display.sizer.style.minWidth = Math.max(0, width + 3 + scrollerCutOff) + "px";
- display.maxLineChanged = false;
- var maxScrollLeft = Math.max(0, display.sizer.offsetLeft + display.sizer.offsetWidth - display.scroller.clientWidth);
- if (maxScrollLeft < doc.scrollLeft && !op.updateScrollPos)
- setScrollLeft(cm, Math.min(display.scroller.scrollLeft, maxScrollLeft), true);
- }
- var newScrollPos, updated;
- if (op.updateScrollPos) {
- newScrollPos = op.updateScrollPos;
- } else if (op.selectionChanged && display.scroller.clientHeight) { // don't rescroll if not visible
- var coords = cursorCoords(cm, doc.sel.head);
- newScrollPos = calculateScrollPos(cm, coords.left, coords.top, coords.left, coords.bottom);
- }
- if (op.changes.length || op.forceUpdate || newScrollPos && newScrollPos.scrollTop != null) {
- updated = updateDisplay(cm, op.changes, newScrollPos && newScrollPos.scrollTop, op.forceUpdate);
+ if (op.updateMaxLine) findMaxLine(cm);
+
+ // If it looks like an update might be needed, call updateDisplay
+ if (op.viewChanged || op.forceUpdate || op.scrollTop != null ||
+ op.scrollToPos && (op.scrollToPos.from.line < display.viewFrom ||
+ op.scrollToPos.to.line >= display.viewTo) ||
+ display.maxLineChanged && cm.options.lineWrapping) {
+ var updated = updateDisplay(cm, {top: op.scrollTop, ensure: op.scrollToPos}, op.forceUpdate);
if (cm.display.scroller.offsetHeight) cm.doc.scrollTop = cm.display.scroller.scrollTop;
}
+ // If no update was run, but the selection changed, redraw that.
if (!updated && op.selectionChanged) updateSelection(cm);
- if (op.updateScrollPos) {
- var top = Math.max(0, Math.min(display.scroller.scrollHeight - display.scroller.clientHeight, newScrollPos.scrollTop));
- var left = Math.max(0, Math.min(display.scroller.scrollWidth - display.scroller.clientWidth, newScrollPos.scrollLeft));
+ if (!updated && op.startHeight != cm.doc.height) updateScrollbars(cm);
+
+ // Propagate the scroll position to the actual DOM scroller
+ if (op.scrollTop != null && display.scroller.scrollTop != op.scrollTop) {
+ var top = Math.max(0, Math.min(display.scroller.scrollHeight - display.scroller.clientHeight, op.scrollTop));
display.scroller.scrollTop = display.scrollbarV.scrollTop = doc.scrollTop = top;
+ }
+ if (op.scrollLeft != null && display.scroller.scrollLeft != op.scrollLeft) {
+ var left = Math.max(0, Math.min(display.scroller.scrollWidth - display.scroller.clientWidth, op.scrollLeft));
display.scroller.scrollLeft = display.scrollbarH.scrollLeft = doc.scrollLeft = left;
alignHorizontally(cm);
- if (op.scrollToPos)
- scrollPosIntoView(cm, clipPos(cm.doc, op.scrollToPos.from),
- clipPos(cm.doc, op.scrollToPos.to), op.scrollToPos.margin);
- } else if (newScrollPos) {
- scrollCursorIntoView(cm);
}
+ // If we need to scroll a specific position into view, do so.
+ if (op.scrollToPos) {
+ var coords = scrollPosIntoView(cm, clipPos(cm.doc, op.scrollToPos.from),
+ clipPos(cm.doc, op.scrollToPos.to), op.scrollToPos.margin);
+ if (op.scrollToPos.isCursor && cm.state.focused) maybeScrollWindow(cm, coords);
+ }
+
if (op.selectionChanged) restartBlink(cm);
if (cm.state.focused && op.updateInput)
- resetInput(cm, op.userSelChange);
+ resetInput(cm, op.typing);
+ // Fire events for markers that are hidden/unidden by editing or
+ // undoing
var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers;
if (hidden) for (var i = 0; i < hidden.length; ++i)
if (!hidden[i].lines.length) signal(hidden[i], "hide");
@@ -1421,47 +1934,242 @@
delayed = delayedCallbacks;
delayedCallbacks = null;
}
- if (op.textChanged)
- signal(cm, "change", cm, op.textChanged);
+ // Fire change events, and delayed event handlers
+ if (op.changeObjs) {
+ for (var i = 0; i < op.changeObjs.length; i++)
+ signal(cm, "change", cm, op.changeObjs[i]);
+ signal(cm, "changes", cm, op.changeObjs);
+ }
if (op.cursorActivity) signal(cm, "cursorActivity", cm);
if (delayed) for (var i = 0; i < delayed.length; ++i) delayed[i]();
}
- // Wraps a function in an operation. Returns the wrapped function.
- function operation(cm1, f) {
- return function() {
- var cm = cm1 || this, withOp = !cm.curOp;
- if (withOp) startOperation(cm);
- try { var result = f.apply(cm, arguments); }
- finally { if (withOp) endOperation(cm); }
- return result;
- };
- }
- function docOperation(f) {
- return function() {
- var withOp = this.cm && !this.cm.curOp, result;
- if (withOp) startOperation(this.cm);
- try { result = f.apply(this, arguments); }
- finally { if (withOp) endOperation(this.cm); }
- return result;
- };
- }
+ // Run the given function in an operation
function runInOp(cm, f) {
- var withOp = !cm.curOp, result;
- if (withOp) startOperation(cm);
- try { result = f(); }
- finally { if (withOp) endOperation(cm); }
- return result;
+ if (cm.curOp) return f();
+ startOperation(cm);
+ try { return f(); }
+ finally { endOperation(cm); }
+ }
+ // Wraps a function in an operation. Returns the wrapped function.
+ function operation(cm, f) {
+ return function() {
+ if (cm.curOp) return f.apply(cm, arguments);
+ startOperation(cm);
+ try { return f.apply(cm, arguments); }
+ finally { endOperation(cm); }
+ };
+ }
+ // Used to add methods to editor and doc instances, wrapping them in
+ // operations.
+ function methodOp(f) {
+ return function() {
+ if (this.curOp) return f.apply(this, arguments);
+ startOperation(this);
+ try { return f.apply(this, arguments); }
+ finally { endOperation(this); }
+ };
+ }
+ function docMethodOp(f) {
+ return function() {
+ var cm = this.cm;
+ if (!cm || cm.curOp) return f.apply(this, arguments);
+ startOperation(cm);
+ try { return f.apply(this, arguments); }
+ finally { endOperation(cm); }
+ };
}
+ // VIEW TRACKING
+
+ // These objects are used to represent the visible (currently drawn)
+ // part of the document. A LineView may correspond to multiple
+ // logical lines, if those are connected by collapsed ranges.
+ function LineView(doc, line, lineN) {
+ // The starting line
+ this.line = line;
+ // Continuing lines, if any
+ this.rest = visualLineContinued(line);
+ // Number of logical lines in this visual line
+ this.size = this.rest ? lineNo(lst(this.rest)) - lineN + 1 : 1;
+ this.node = this.text = null;
+ this.hidden = lineIsHidden(doc, line);
+ }
+
+ // Create a range of LineView objects for the given lines.
+ function buildViewArray(cm, from, to) {
+ var array = [], nextPos;
+ for (var pos = from; pos < to; pos = nextPos) {
+ var view = new LineView(cm.doc, getLine(cm.doc, pos), pos);
+ nextPos = pos + view.size;
+ array.push(view);
+ }
+ return array;
+ }
+
+ // Updates the display.view data structure for a given change to the
+ // document. From and to are in pre-change coordinates. Lendiff is
+ // the amount of lines added or subtracted by the change. This is
+ // used for changes that span multiple lines, or change the way
+ // lines are divided into visual lines. regLineChange (below)
+ // registers single-line changes.
function regChange(cm, from, to, lendiff) {
if (from == null) from = cm.doc.first;
if (to == null) to = cm.doc.first + cm.doc.size;
- cm.curOp.changes.push({from: from, to: to, diff: lendiff});
+ if (!lendiff) lendiff = 0;
+
+ var display = cm.display;
+ if (lendiff && to < display.viewTo &&
+ (display.updateLineNumbers == null || display.updateLineNumbers > from))
+ display.updateLineNumbers = from;
+
+ cm.curOp.viewChanged = true;
+
+ if (from >= display.viewTo) { // Change after
+ if (sawCollapsedSpans && visualLineNo(cm.doc, from) < display.viewTo)
+ resetView(cm);
+ } else if (to <= display.viewFrom) { // Change before
+ if (sawCollapsedSpans && visualLineEndNo(cm.doc, to + lendiff) > display.viewFrom) {
+ resetView(cm);
+ } else {
+ display.viewFrom += lendiff;
+ display.viewTo += lendiff;
+ }
+ } else if (from <= display.viewFrom && to >= display.viewTo) { // Full overlap
+ resetView(cm);
+ } else if (from <= display.viewFrom) { // Top overlap
+ var cut = viewCuttingPoint(cm, to, to + lendiff, 1);
+ if (cut) {
+ display.view = display.view.slice(cut.index);
+ display.viewFrom = cut.lineN;
+ display.viewTo += lendiff;
+ } else {
+ resetView(cm);
+ }
+ } else if (to >= display.viewTo) { // Bottom overlap
+ var cut = viewCuttingPoint(cm, from, from, -1);
+ if (cut) {
+ display.view = display.view.slice(0, cut.index);
+ display.viewTo = cut.lineN;
+ } else {
+ resetView(cm);
+ }
+ } else { // Gap in the middle
+ var cutTop = viewCuttingPoint(cm, from, from, -1);
+ var cutBot = viewCuttingPoint(cm, to, to + lendiff, 1);
+ if (cutTop && cutBot) {
+ display.view = display.view.slice(0, cutTop.index)
+ .concat(buildViewArray(cm, cutTop.lineN, cutBot.lineN))
+ .concat(display.view.slice(cutBot.index));
+ display.viewTo += lendiff;
+ } else {
+ resetView(cm);
+ }
+ }
+
+ var ext = display.externalMeasured;
+ if (ext) {
+ if (to < ext.lineN)
+ ext.lineN += lendiff;
+ else if (from < ext.lineN + ext.size)
+ display.externalMeasured = null;
+ }
+ }
+
+ // Register a change to a single line. Type must be one of "text",
+ // "gutter", "class", "widget"
+ function regLineChange(cm, line, type) {
+ cm.curOp.viewChanged = true;
+ var display = cm.display, ext = cm.display.externalMeasured;
+ if (ext && line >= ext.lineN && line < ext.lineN + ext.size)
+ display.externalMeasured = null;
+
+ if (line < display.viewFrom || line >= display.viewTo) return;
+ var lineView = display.view[findViewIndex(cm, line)];
+ if (lineView.node == null) return;
+ var arr = lineView.changes || (lineView.changes = []);
+ if (indexOf(arr, type) == -1) arr.push(type);
+ }
+
+ // Clear the view.
+ function resetView(cm) {
+ cm.display.viewFrom = cm.display.viewTo = cm.doc.first;
+ cm.display.view = [];
+ cm.display.viewOffset = 0;
+ }
+
+ // Find the view element corresponding to a given line. Return null
+ // when the line isn't visible.
+ function findViewIndex(cm, n) {
+ if (n >= cm.display.viewTo) return null;
+ n -= cm.display.viewFrom;
+ if (n < 0) return null;
+ var view = cm.display.view;
+ for (var i = 0; i < view.length; i++) {
+ n -= view[i].size;
+ if (n < 0) return i;
+ }
+ }
+
+ function viewCuttingPoint(cm, oldN, newN, dir) {
+ var index = findViewIndex(cm, oldN), diff, view = cm.display.view;
+ if (!sawCollapsedSpans) return {index: index, lineN: newN};
+ for (var i = 0, n = cm.display.viewFrom; i < index; i++)
+ n += view[i].size;
+ if (n != oldN) {
+ if (dir > 0) {
+ if (index == view.length - 1) return null;
+ diff = (n + view[index].size) - oldN;
+ index++;
+ } else {
+ diff = n - oldN;
+ }
+ oldN += diff; newN += diff;
+ }
+ while (visualLineNo(cm.doc, newN) != newN) {
+ if (index == (dir < 0 ? 0 : view.length - 1)) return null;
+ newN += dir * view[index - (dir < 0 ? 1 : 0)].size;
+ index += dir;
+ }
+ return {index: index, lineN: newN};
+ }
+
+ // Force the view to cover a given range, adding empty view element
+ // or clipping off existing ones as needed.
+ function adjustView(cm, from, to) {
+ var display = cm.display, view = display.view;
+ if (view.length == 0 || from >= display.viewTo || to <= display.viewFrom) {
+ display.view = buildViewArray(cm, from, to);
+ display.viewFrom = from;
+ } else {
+ if (display.viewFrom > from)
+ display.view = buildViewArray(cm, from, display.viewFrom).concat(display.view);
+ else if (display.viewFrom < from)
+ display.view = display.view.slice(findViewIndex(cm, from));
+ display.viewFrom = from;
+ if (display.viewTo < to)
+ display.view = display.view.concat(buildViewArray(cm, display.viewTo, to));
+ else if (display.viewTo > to)
+ display.view = display.view.slice(0, findViewIndex(cm, to));
+ }
+ display.viewTo = to;
+ }
+
+ // Count the number of lines in the view whose DOM representation is
+ // out of date (or nonexistent).
+ function countDirtyView(cm) {
+ var view = cm.display.view, dirty = 0;
+ for (var i = 0; i < view.length; i++) {
+ var lineView = view[i];
+ if (!lineView.hidden && (!lineView.node || lineView.changes)) ++dirty;
+ }
+ return dirty;
}
// INPUT HANDLING
+ // Poll for input changes, using the normal rate of polling. This
+ // runs as long as the editor is focused.
function slowPoll(cm) {
if (cm.display.pollingFast) return;
cm.display.poll.set(cm.options.pollInterval, function() {
@@ -1470,6 +2178,9 @@
});
}
+ // When an event has just come in that is likely to add or change
+ // something in the input textarea, we poll faster, to ensure that
+ // the change appears on the screen quickly.
function fastPoll(cm) {
var missed = false;
cm.display.pollingFast = true;
@@ -1481,53 +2192,72 @@
cm.display.poll.set(20, p);
}
- // prevInput is a hack to work with IME. If we reset the textarea
- // on every change, that breaks IME. So we look for changes
- // compared to the previous content instead. (Modern browsers have
- // events that indicate IME taking place, but these are not widely
- // supported or compatible enough yet to rely on.)
+ // Read input from the textarea, and update the document to match.
+ // When something is selected, it is present in the textarea, and
+ // selected (unless it is huge, in which case a placeholder is
+ // used). When nothing is selected, the cursor sits after previously
+ // seen text (can be empty), which is stored in prevInput (we must
+ // not reset the textarea when typing, because that breaks IME).
function readInput(cm) {
- var input = cm.display.input, prevInput = cm.display.prevInput, doc = cm.doc, sel = doc.sel;
+ var input = cm.display.input, prevInput = cm.display.prevInput, doc = cm.doc;
+ // Since this is called a *lot*, try to bail out as cheaply as
+ // possible when it is clear that nothing happened. hasSelection
+ // will be the case when there is a lot of text in the textarea,
+ // in which case reading its value would be expensive.
if (!cm.state.focused || hasSelection(input) || isReadOnly(cm) || cm.options.disableInput) return false;
- if (cm.state.pasteIncoming && cm.state.fakedLastChar) {
- input.value = input.value.substring(0, input.value.length - 1);
- cm.state.fakedLastChar = false;
- }
var text = input.value;
- if (text == prevInput && posEq(sel.from, sel.to)) return false;
- if (ie && !ie_lt9 && cm.display.inputHasSelection === text) {
- resetInput(cm, true);
+ // If nothing changed, bail.
+ if (text == prevInput && !cm.somethingSelected()) return false;
+ // Work around nonsensical selection resetting in IE9/10
+ if (ie && !ie_upto8 && cm.display.inputHasSelection === text) {
+ resetInput(cm);
return false;
}
var withOp = !cm.curOp;
if (withOp) startOperation(cm);
- sel.shift = false;
+ cm.display.shift = false;
+
+ // Find the part of the input that is actually new
var same = 0, l = Math.min(prevInput.length, text.length);
while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same;
- var from = sel.from, to = sel.to;
- var inserted = text.slice(same);
- if (same < prevInput.length)
- from = Pos(from.line, from.ch - (prevInput.length - same));
- else if (cm.state.overwrite && posEq(from, to) && !cm.state.pasteIncoming)
- to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + inserted.length));
+ var inserted = text.slice(same), textLines = splitLines(inserted);
- var updateInput = cm.curOp.updateInput;
- var changeEvent = {from: from, to: to, text: splitLines(inserted),
- origin: cm.state.pasteIncoming ? "paste" : cm.state.cutIncoming ? "cut" : "+input"};
- makeChange(cm.doc, changeEvent, "end");
- cm.curOp.updateInput = updateInput;
- signalLater(cm, "inputRead", cm, changeEvent);
- if (inserted && !cm.state.pasteIncoming && cm.options.electricChars &&
- cm.options.smartIndent && sel.head.ch < 100) {
- var electric = cm.getModeAt(sel.head).electricChars;
- if (electric) for (var i = 0; i < electric.length; i++)
- if (inserted.indexOf(electric.charAt(i)) > -1) {
- indentLine(cm, sel.head.line, "smart");
- break;
- }
+ // When pasing N lines into N selections, insert one line per selection
+ var multiPaste = cm.state.pasteIncoming && textLines.length > 1 && doc.sel.ranges.length == textLines.length;
+
+ // Normal behavior is to insert the new text into every selection
+ for (var i = doc.sel.ranges.length - 1; i >= 0; i--) {
+ var range = doc.sel.ranges[i];
+ var from = range.from(), to = range.to();
+ // Handle deletion
+ if (same < prevInput.length)
+ from = Pos(from.line, from.ch - (prevInput.length - same));
+ // Handle overwrite
+ else if (cm.state.overwrite && range.empty() && !cm.state.pasteIncoming)
+ to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length));
+ var updateInput = cm.curOp.updateInput;
+ var changeEvent = {from: from, to: to, text: multiPaste ? [textLines[i]] : textLines,
+ origin: cm.state.pasteIncoming ? "paste" : cm.state.cutIncoming ? "cut" : "+input"};
+ makeChange(cm.doc, changeEvent);
+ signalLater(cm, "inputRead", cm, changeEvent);
+ // When an 'electric' character is inserted, immediately trigger a reindent
+ if (inserted && !cm.state.pasteIncoming && cm.options.electricChars &&
+ cm.options.smartIndent && range.head.ch < 100 &&
+ (!i || doc.sel.ranges[i - 1].head.line != range.head.line)) {
+ var electric = cm.getModeAt(range.head).electricChars;
+ if (electric) for (var j = 0; j < electric.length; j++)
+ if (inserted.indexOf(electric.charAt(j)) > -1) {
+ indentLine(cm, range.head.line, "smart");
+ break;
+ }
+ }
}
+ ensureCursorVisible(cm);
+ cm.curOp.updateInput = updateInput;
+ cm.curOp.typing = true;
+ // Don't leave long text in the textarea, since it makes further polling slow
if (text.length > 1000 || text.indexOf("\n") > -1) input.value = cm.display.prevInput = "";
else cm.display.prevInput = text;
if (withOp) endOperation(cm);
@@ -1535,56 +2265,68 @@
return true;
}
- function resetInput(cm, user) {
+ // Reset the input to correspond to the selection (or to be empty,
+ // when not typing and nothing is selected)
+ function resetInput(cm, typing) {
var minimal, selected, doc = cm.doc;
- if (!posEq(doc.sel.from, doc.sel.to)) {
+ if (cm.somethingSelected()) {
cm.display.prevInput = "";
+ var range = doc.sel.primary();
minimal = hasCopyEvent &&
- (doc.sel.to.line - doc.sel.from.line > 100 || (selected = cm.getSelection()).length > 1000);
+ (range.to().line - range.from().line > 100 || (selected = cm.getSelection()).length > 1000);
var content = minimal ? "-" : selected || cm.getSelection();
cm.display.input.value = content;
if (cm.state.focused) selectInput(cm.display.input);
- if (ie && !ie_lt9) cm.display.inputHasSelection = content;
- } else if (user) {
+ if (ie && !ie_upto8) cm.display.inputHasSelection = content;
+ } else if (!typing) {
cm.display.prevInput = cm.display.input.value = "";
- if (ie && !ie_lt9) cm.display.inputHasSelection = null;
+ if (ie && !ie_upto8) cm.display.inputHasSelection = null;
}
cm.display.inaccurateSelection = minimal;
}
function focusInput(cm) {
- if (cm.options.readOnly != "nocursor" && (!mobile || document.activeElement != cm.display.input))
+ if (cm.options.readOnly != "nocursor" && (!mobile || activeElt() != cm.display.input))
cm.display.input.focus();
}
+ function ensureFocus(cm) {
+ if (!cm.state.focused) { focusInput(cm); onFocus(cm); }
+ }
+
function isReadOnly(cm) {
return cm.options.readOnly || cm.doc.cantEdit;
}
// EVENT HANDLERS
+ // Attach the necessary event handlers when initializing the editor
function registerEventHandlers(cm) {
var d = cm.display;
on(d.scroller, "mousedown", operation(cm, onMouseDown));
- if (old_ie)
+ // Older IE's will not fire a second mousedown for a double click
+ if (ie_upto10)
on(d.scroller, "dblclick", operation(cm, function(e) {
if (signalDOMEvent(cm, e)) return;
var pos = posFromMouse(cm, e);
if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) return;
e_preventDefault(e);
- var word = findWordAt(getLine(cm.doc, pos.line).text, pos);
- extendSelection(cm.doc, word.from, word.to);
+ var word = findWordAt(cm.doc, pos);
+ extendSelection(cm.doc, word.anchor, word.head);
}));
else
on(d.scroller, "dblclick", function(e) { signalDOMEvent(cm, e) || e_preventDefault(e); });
+ // Prevent normal selection in the editor (we handle our own)
on(d.lineSpace, "selectstart", function(e) {
if (!eventInWidget(d, e)) e_preventDefault(e);
});
- // Gecko browsers fire contextmenu *after* opening the menu, at
+ // Some browsers fire contextmenu *after* opening the menu, at
// which point we can't mess with it anymore. Context menu is
- // handled in onMouseDown for Gecko.
- if (!captureMiddleClick) on(d.scroller, "contextmenu", function(e) {onContextMenu(cm, e);});
+ // handled in onMouseDown for these browsers.
+ if (!captureRightClick) on(d.scroller, "contextmenu", function(e) {onContextMenu(cm, e);});
+ // Sync scrolling between fake scrollbars and real scrollable
+ // area, ensure viewport is updated when scrolling.
on(d.scroller, "scroll", function() {
if (d.scroller.clientHeight) {
setScrollTop(cm, d.scroller.scrollTop);
@@ -1599,39 +2341,40 @@
if (d.scroller.clientHeight) setScrollLeft(cm, d.scrollbarH.scrollLeft);
});
+ // Listen to wheel events in order to try and update the viewport on time.
on(d.scroller, "mousewheel", function(e){onScrollWheel(cm, e);});
on(d.scroller, "DOMMouseScroll", function(e){onScrollWheel(cm, e);});
+ // Prevent clicks in the scrollbars from killing focus
function reFocus() { if (cm.state.focused) setTimeout(bind(focusInput, cm), 0); }
on(d.scrollbarH, "mousedown", reFocus);
on(d.scrollbarV, "mousedown", reFocus);
// Prevent wrapper from ever scrolling
on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; });
+ // When the window resizes, we need to refresh active editors.
var resizeTimer;
function onResize() {
if (resizeTimer == null) resizeTimer = setTimeout(function() {
resizeTimer = null;
// Might be a text scaling operation, clear size caches.
- d.cachedCharWidth = d.cachedTextHeight = knownScrollbarWidth = null;
- clearCaches(cm);
- runInOp(cm, bind(regChange, cm));
+ d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = knownScrollbarWidth = null;
+ cm.setSize();
}, 100);
}
on(window, "resize", onResize);
- // Above handler holds on to the editor and its data structures.
- // Here we poll to unregister it when the editor is no longer in
- // the document, so that it can be garbage-collected.
+ // The above handler holds on to the editor and its data
+ // structures. Here we poll to unregister it when the editor is no
+ // longer in the document, so that it can be garbage-collected.
function unregister() {
- for (var p = d.wrapper.parentNode; p && p != document.body; p = p.parentNode) {}
- if (p) setTimeout(unregister, 5000);
+ if (contains(document.body, d.wrapper)) setTimeout(unregister, 5000);
else off(window, "resize", onResize);
}
setTimeout(unregister, 5000);
on(d.input, "keyup", operation(cm, onKeyUp));
on(d.input, "input", function() {
- if (ie && !ie_lt9 && cm.display.inputHasSelection) cm.display.inputHasSelection = null;
+ if (ie && !ie_upto8 && cm.display.inputHasSelection) cm.display.inputHasSelection = null;
fastPoll(cm);
});
on(d.input, "keydown", operation(cm, onKeyDown));
@@ -1640,8 +2383,7 @@
on(d.input, "blur", bind(onBlur, cm));
function drag_(e) {
- if (signalDOMEvent(cm, e) || cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))) return;
- e_stop(e);
+ if (!signalDOMEvent(cm, e)) e_stop(e);
}
if (cm.options.dragDrop) {
on(d.scroller, "dragstart", function(e){onDragStart(cm, e);});
@@ -1651,20 +2393,11 @@
}
on(d.scroller, "paste", function(e) {
if (eventInWidget(d, e)) return;
+ cm.state.pasteIncoming = true;
focusInput(cm);
fastPoll(cm);
});
on(d.input, "paste", function() {
- // Workaround for webkit bug https://bugs.webkit.org/show_bug.cgi?id=90206
- // Add a char to the end of textarea before paste occur so that
- // selection doesn't span to the end of textarea.
- if (webkit && !cm.state.fakedLastChar && !(new Date - cm.state.lastMiddleDown < 200)) {
- var start = d.input.selectionStart, end = d.input.selectionEnd;
- d.input.value += "$";
- d.input.selectionStart = start;
- d.input.selectionEnd = end;
- cm.state.fakedLastChar = true;
- }
cm.state.pasteIncoming = true;
fastPoll(cm);
});
@@ -1683,39 +2416,58 @@
// Needed to handle Tab key in KHTML
if (khtml) on(d.sizer, "mouseup", function() {
- if (document.activeElement == d.input) d.input.blur();
+ if (activeElt() == d.input) d.input.blur();
focusInput(cm);
});
}
+ // MOUSE EVENTS
+
+ // Return true when the given mouse event happened in a widget
function eventInWidget(display, e) {
for (var n = e_target(e); n != display.wrapper; n = n.parentNode) {
if (!n || n.ignoreEvents || n.parentNode == display.sizer && n != display.mover) return true;
}
}
- function posFromMouse(cm, e, liberal) {
+ // Given a mouse event, find the corresponding position. If liberal
+ // is false, it checks whether a gutter or scrollbar was clicked,
+ // and returns null if it was. forRect is used by rectangular
+ // selections, and tries to estimate a character position even for
+ // coordinates beyond the right of the text.
+ function posFromMouse(cm, e, liberal, forRect) {
var display = cm.display;
if (!liberal) {
var target = e_target(e);
- if (target == display.scrollbarH || target == display.scrollbarH.firstChild ||
- target == display.scrollbarV || target == display.scrollbarV.firstChild ||
+ if (target == display.scrollbarH || target == display.scrollbarV ||
target == display.scrollbarFiller || target == display.gutterFiller) return null;
}
- var x, y, space = getRect(display.lineSpace);
+ var x, y, space = display.lineSpace.getBoundingClientRect();
// Fails unpredictably on IE[67] when mouse is dragged around quickly.
- try { x = e.clientX; y = e.clientY; } catch (e) { return null; }
- return coordsChar(cm, x - space.left, y - space.top);
+ try { x = e.clientX - space.left; y = e.clientY - space.top; }
+ catch (e) { return null; }
+ var coords = coordsChar(cm, x, y), line;
+ if (forRect && coords.xRel == 1 && (line = getLine(cm.doc, coords.line).text).length == coords.ch) {
+ var colDiff = countColumn(line, line.length, cm.options.tabSize) - line.length;
+ coords = Pos(coords.line, Math.round((x - paddingH(cm.display).left) / charWidth(cm.display)) - colDiff);
+ }
+ return coords;
}
- var lastClick, lastDoubleClick;
+ // A mouse down can be a single click, double click, triple click,
+ // start of selection drag, start of text drag, new cursor
+ // (ctrl-click), rectangle drag (alt-drag), or xwin
+ // middle-click-paste. Or it might be a click on something we should
+ // not interfere with, such as a scrollbar or widget.
function onMouseDown(e) {
if (signalDOMEvent(this, e)) return;
- var cm = this, display = cm.display, doc = cm.doc, sel = doc.sel;
- sel.shift = e.shiftKey;
+ var cm = this, display = cm.display;
+ display.shift = e.shiftKey;
if (eventInWidget(display, e)) {
if (!webkit) {
+ // Briefly turn off draggability, to allow widgets to do
+ // normal dragging things.
display.scroller.draggable = false;
setTimeout(function(){display.scroller.draggable = true;}, 100);
}
@@ -1723,92 +2475,168 @@
}
if (clickInGutter(cm, e)) return;
var start = posFromMouse(cm, e);
+ window.focus();
switch (e_button(e)) {
- case 3:
- if (captureMiddleClick) onContextMenu.call(cm, cm, e);
- return;
+ case 1:
+ if (start)
+ leftButtonDown(cm, e, start);
+ else if (e_target(e) == display.scroller)
+ e_preventDefault(e);
+ break;
case 2:
if (webkit) cm.state.lastMiddleDown = +new Date;
if (start) extendSelection(cm.doc, start);
setTimeout(bind(focusInput, cm), 20);
e_preventDefault(e);
- return;
+ break;
+ case 3:
+ if (captureRightClick) onContextMenu(cm, e);
+ break;
}
- // For button 1, if it was clicked inside the editor
- // (posFromMouse returning non-null), we have to adjust the
- // selection.
- if (!start) {if (e_target(e) == display.scroller) e_preventDefault(e); return;}
+ }
- if (!cm.state.focused) onFocus(cm);
+ var lastClick, lastDoubleClick;
+ function leftButtonDown(cm, e, start) {
+ setTimeout(bind(ensureFocus, cm), 0);
- var now = +new Date, type = "single";
- if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) {
+ var now = +new Date, type;
+ if (lastDoubleClick && lastDoubleClick.time > now - 400 && cmp(lastDoubleClick.pos, start) == 0) {
type = "triple";
- e_preventDefault(e);
- setTimeout(bind(focusInput, cm), 20);
- selectLine(cm, start.line);
- } else if (lastClick && lastClick.time > now - 400 && posEq(lastClick.pos, start)) {
+ } else if (lastClick && lastClick.time > now - 400 && cmp(lastClick.pos, start) == 0) {
type = "double";
lastDoubleClick = {time: now, pos: start};
- e_preventDefault(e);
- var word = findWordAt(getLine(doc, start.line).text, start);
- extendSelection(cm.doc, word.from, word.to);
- } else { lastClick = {time: now, pos: start}; }
-
- var last = start;
- if (cm.options.dragDrop && dragAndDrop && !isReadOnly(cm) && !posEq(sel.from, sel.to) &&
- !posLess(start, sel.from) && !posLess(sel.to, start) && type == "single") {
- var dragEnd = operation(cm, function(e2) {
- if (webkit) display.scroller.draggable = false;
- cm.state.draggingText = false;
- off(document, "mouseup", dragEnd);
- off(display.scroller, "drop", dragEnd);
- if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) {
- e_preventDefault(e2);
- extendSelection(cm.doc, start);
- focusInput(cm);
- // Work around unexplainable focus problem in IE9 (#2127)
- if (old_ie && !ie_lt9)
- setTimeout(function() {document.body.focus(); focusInput(cm);}, 20);
- }
- });
- // Let the drag handler handle this.
- if (webkit) display.scroller.draggable = true;
- cm.state.draggingText = dragEnd;
- // IE's approach to draggable
- if (display.scroller.dragDrop) display.scroller.dragDrop();
- on(document, "mouseup", dragEnd);
- on(display.scroller, "drop", dragEnd);
- return;
+ } else {
+ type = "single";
+ lastClick = {time: now, pos: start};
}
+
+ var sel = cm.doc.sel, addNew = mac ? e.metaKey : e.ctrlKey;
+ if (cm.options.dragDrop && dragAndDrop && !addNew && !isReadOnly(cm) &&
+ type == "single" && sel.contains(start) > -1 && sel.somethingSelected())
+ leftButtonStartDrag(cm, e, start);
+ else
+ leftButtonSelect(cm, e, start, type, addNew);
+ }
+
+ // Start a text drag. When it ends, see if any dragging actually
+ // happen, and treat as a click if it didn't.
+ function leftButtonStartDrag(cm, e, start) {
+ var display = cm.display;
+ var dragEnd = operation(cm, function(e2) {
+ if (webkit) display.scroller.draggable = false;
+ cm.state.draggingText = false;
+ off(document, "mouseup", dragEnd);
+ off(display.scroller, "drop", dragEnd);
+ if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) {
+ e_preventDefault(e2);
+ extendSelection(cm.doc, start);
+ focusInput(cm);
+ // Work around unexplainable focus problem in IE9 (#2127)
+ if (ie_upto10 && !ie_upto8)
+ setTimeout(function() {document.body.focus(); focusInput(cm);}, 20);
+ }
+ });
+ // Let the drag handler handle this.
+ if (webkit) display.scroller.draggable = true;
+ cm.state.draggingText = dragEnd;
+ // IE's approach to draggable
+ if (display.scroller.dragDrop) display.scroller.dragDrop();
+ on(document, "mouseup", dragEnd);
+ on(display.scroller, "drop", dragEnd);
+ }
+
+ // Normal selection, as opposed to text dragging.
+ function leftButtonSelect(cm, e, start, type, addNew) {
+ var display = cm.display, doc = cm.doc;
e_preventDefault(e);
- if (type == "single") extendSelection(cm.doc, clipPos(doc, start));
- var startstart = sel.from, startend = sel.to, lastPos = start;
+ var ourRange, ourIndex, startSel = doc.sel;
+ if (addNew) {
+ ourIndex = doc.sel.contains(start);
+ if (ourIndex > -1)
+ ourRange = doc.sel.ranges[ourIndex];
+ else
+ ourRange = new Range(start, start);
+ } else {
+ ourRange = doc.sel.primary();
+ }
- function doSelect(cur) {
- if (posEq(lastPos, cur)) return;
- lastPos = cur;
+ if (e.altKey) {
+ type = "rect";
+ if (!addNew) ourRange = new Range(start, start);
+ start = posFromMouse(cm, e, true, true);
+ ourIndex = -1;
+ } else if (type == "double") {
+ var word = findWordAt(doc, start);
+ if (cm.display.shift || doc.extend)
+ ourRange = extendRange(doc, ourRange, word.anchor, word.head);
+ else
+ ourRange = word;
+ } else if (type == "triple") {
+ var line = new Range(Pos(start.line, 0), clipPos(doc, Pos(start.line + 1, 0)));
+ if (cm.display.shift || doc.extend)
+ ourRange = extendRange(doc, ourRange, line.anchor, line.head);
+ else
+ ourRange = line;
+ } else {
+ ourRange = extendRange(doc, ourRange, start);
+ }
- if (type == "single") {
- extendSelection(cm.doc, clipPos(doc, start), cur);
- return;
- }
+ if (!addNew) {
+ ourIndex = 0;
+ setSelection(doc, new Selection([ourRange], 0), sel_mouse);
+ } else if (ourIndex > -1) {
+ replaceOneSelection(doc, ourIndex, ourRange, sel_mouse);
+ } else {
+ ourIndex = doc.sel.ranges.length;
+ setSelection(doc, normalizeSelection(doc.sel.ranges.concat([ourRange]), ourIndex),
+ {scroll: false, origin: "*mouse"});
+ }
- startstart = clipPos(doc, startstart);
- startend = clipPos(doc, startend);
- if (type == "double") {
- var word = findWordAt(getLine(doc, cur.line).text, cur);
- if (posLess(cur, startstart)) extendSelection(cm.doc, word.from, startend);
- else extendSelection(cm.doc, startstart, word.to);
- } else if (type == "triple") {
- if (posLess(cur, startstart)) extendSelection(cm.doc, startend, clipPos(doc, Pos(cur.line, 0)));
- else extendSelection(cm.doc, startstart, clipPos(doc, Pos(cur.line + 1, 0)));
+ var lastPos = start;
+ function extendTo(pos) {
+ if (cmp(lastPos, pos) == 0) return;
+ lastPos = pos;
+
+ if (type == "rect") {
+ var ranges = [], tabSize = cm.options.tabSize;
+ var startCol = countColumn(getLine(doc, start.line).text, start.ch, tabSize);
+ var posCol = countColumn(getLine(doc, pos.line).text, pos.ch, tabSize);
+ var left = Math.min(startCol, posCol), right = Math.max(startCol, posCol);
+ for (var line = Math.min(start.line, pos.line), end = Math.min(cm.lastLine(), Math.max(start.line, pos.line));
+ line <= end; line++) {
+ var text = getLine(doc, line).text, leftPos = findColumn(text, left, tabSize);
+ if (left == right)
+ ranges.push(new Range(Pos(line, leftPos), Pos(line, leftPos)));
+ else if (text.length > leftPos)
+ ranges.push(new Range(Pos(line, leftPos), Pos(line, findColumn(text, right, tabSize))));
+ }
+ if (!ranges.length) ranges.push(new Range(start, start));
+ setSelection(doc, normalizeSelection(startSel.ranges.slice(0, ourIndex).concat(ranges), ourIndex), sel_mouse);
+ } else {
+ var oldRange = ourRange;
+ var anchor = oldRange.anchor, head = pos;
+ if (type != "single") {
+ if (type == "double")
+ var range = findWordAt(doc, pos);
+ else
+ var range = new Range(Pos(pos.line, 0), clipPos(doc, Pos(pos.line + 1, 0)));
+ if (cmp(range.anchor, anchor) > 0) {
+ head = range.head;
+ anchor = minPos(oldRange.from(), range.anchor);
+ } else {
+ head = range.anchor;
+ anchor = maxPos(oldRange.to(), range.head);
+ }
+ }
+ var ranges = startSel.ranges.slice(0);
+ ranges[ourIndex] = new Range(clipPos(doc, anchor), head);
+ setSelection(doc, normalizeSelection(ranges, ourIndex), sel_mouse);
}
}
- var editorSize = getRect(display.wrapper);
+ var editorSize = display.wrapper.getBoundingClientRect();
// Used to ensure timeout re-tries don't fire when another extend
// happened in the meantime (clearTimeout isn't reliable -- at
// least on Chrome, the timeouts still happen even when cleared,
@@ -1817,12 +2645,11 @@
function extend(e) {
var curCount = ++counter;
- var cur = posFromMouse(cm, e, true);
+ var cur = posFromMouse(cm, e, true, type == "rect");
if (!cur) return;
- if (!posEq(cur, last)) {
- if (!cm.state.focused) onFocus(cm);
- last = cur;
- doSelect(cur);
+ if (cmp(cur, lastPos) != 0) {
+ ensureFocus(cm);
+ extendTo(cur);
var visible = visibleLines(display, doc);
if (cur.line >= visible.to || cur.line < visible.from)
setTimeout(operation(cm, function(){if (counter == curCount) extend(e);}), 150);
@@ -1842,10 +2669,11 @@
focusInput(cm);
off(document, "mousemove", move);
off(document, "mouseup", up);
+ doc.history.lastSelOrigin = null;
}
var move = operation(cm, function(e) {
- if (!old_ie && !e_button(e)) done(e);
+ if ((ie && !ie_upto9) ? !e.buttons : !e_button(e)) done(e);
else extend(e);
});
var up = operation(cm, done);
@@ -1853,21 +2681,23 @@
on(document, "mouseup", up);
}
+ // Determines whether an event happened in the gutter, and fires the
+ // handlers for the corresponding event.
function gutterEvent(cm, e, type, prevent, signalfn) {
try { var mX = e.clientX, mY = e.clientY; }
catch(e) { return false; }
- if (mX >= Math.floor(getRect(cm.display.gutters).right)) return false;
+ if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) return false;
if (prevent) e_preventDefault(e);
var display = cm.display;
- var lineBox = getRect(display.lineDiv);
+ var lineBox = display.lineDiv.getBoundingClientRect();
if (mY > lineBox.bottom || !hasHandler(cm, type)) return e_defaultPrevented(e);
mY -= lineBox.top - display.viewOffset;
for (var i = 0; i < cm.options.gutters.length; ++i) {
var g = display.gutters.childNodes[i];
- if (g && getRect(g).right >= mX) {
+ if (g && g.getBoundingClientRect().right >= mX) {
var line = lineAtHeight(cm.doc, mY);
var gutter = cm.options.gutters[i];
signalfn(cm, type, cm, line, gutter, e);
@@ -1876,11 +2706,6 @@
}
}
- function contextMenuInGutter(cm, e) {
- if (!hasHandler(cm, "gutterContextMenu")) return false;
- return gutterEvent(cm, e, "gutterContextMenu", false, signal);
- }
-
function clickInGutter(cm, e) {
return gutterEvent(cm, e, "gutterClick", true, signalLater);
}
@@ -1891,12 +2716,14 @@
function onDrop(e) {
var cm = this;
- if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e) || (cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))))
+ if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e))
return;
e_preventDefault(e);
- if (ie) lastDrop = +new Date;
+ if (ie_upto10) lastDrop = +new Date;
var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files;
if (!pos || isReadOnly(cm)) return;
+ // Might be a file drop, in which case we simply extract the text
+ // and insert it.
if (files && files.length && window.FileReader && window.File) {
var n = files.length, text = Array(n), read = 0;
var loadFile = function(file, i) {
@@ -1905,15 +2732,17 @@
text[i] = reader.result;
if (++read == n) {
pos = clipPos(cm.doc, pos);
- makeChange(cm.doc, {from: pos, to: pos, text: splitLines(text.join("\n")), origin: "paste"}, "around");
+ var change = {from: pos, to: pos, text: splitLines(text.join("\n")), origin: "paste"};
+ makeChange(cm.doc, change);
+ setSelectionReplaceHistory(cm.doc, simpleSelection(pos, changeEnd(change)));
}
};
reader.readAsText(file);
};
for (var i = 0; i < n; ++i) loadFile(files[i], i);
- } else {
+ } else { // Normal drop
// Don't do a replace if the drop happened inside of the selected text.
- if (cm.state.draggingText && !(posLess(pos, cm.doc.sel.from) || posLess(cm.doc.sel.to, pos))) {
+ if (cm.state.draggingText && cm.doc.sel.contains(pos) > -1) {
cm.state.draggingText(e);
// Ensure the editor is re-focused
setTimeout(bind(focusInput, cm), 20);
@@ -1922,10 +2751,11 @@
try {
var text = e.dataTransfer.getData("Text");
if (text) {
- var curFrom = cm.doc.sel.from, curTo = cm.doc.sel.to;
- setSelection(cm.doc, pos, pos);
- if (cm.state.draggingText) replaceRange(cm.doc, "", curFrom, curTo, "paste");
- cm.replaceSelection(text, null, "paste");
+ var selected = cm.state.draggingText && cm.listSelections();
+ setSelectionNoUndo(cm.doc, simpleSelection(pos, pos));
+ if (selected) for (var i = 0; i < selected.length; ++i)
+ replaceRange(cm.doc, "", selected[i].anchor, selected[i].head, "drag");
+ cm.replaceSelection(text, "around", "paste");
focusInput(cm);
}
}
@@ -1934,37 +2764,42 @@
}
function onDragStart(cm, e) {
- if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return; }
+ if (ie_upto10 && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return; }
if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) return;
- var txt = cm.getSelection();
- e.dataTransfer.setData("Text", txt);
+ e.dataTransfer.setData("Text", cm.getSelection());
// Use dummy image instead of default browsers image.
// Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there.
if (e.dataTransfer.setDragImage && !safari) {
var img = elt("img", null, null, "position: fixed; left: 0; top: 0;");
img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
- if (opera) {
+ if (presto) {
img.width = img.height = 1;
cm.display.wrapper.appendChild(img);
// Force a relayout, or Opera won't use our image for some obscure reason
img._top = img.offsetTop;
}
e.dataTransfer.setDragImage(img, 0, 0);
- if (opera) img.parentNode.removeChild(img);
+ if (presto) img.parentNode.removeChild(img);
}
}
+ // SCROLL EVENTS
+
+ // Sync the scrollable area and scrollbars, ensure the viewport
+ // covers the visible area.
function setScrollTop(cm, val) {
if (Math.abs(cm.doc.scrollTop - val) < 2) return;
cm.doc.scrollTop = val;
- if (!gecko) updateDisplay(cm, [], val);
+ if (!gecko) updateDisplay(cm, {top: val});
if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = val;
if (cm.display.scrollbarV.scrollTop != val) cm.display.scrollbarV.scrollTop = val;
- if (gecko) updateDisplay(cm, []);
+ if (gecko) updateDisplay(cm);
startWorker(cm, 100);
}
+ // Sync scroller and scrollbar, ensure the gutter elements are
+ // aligned.
function setScrollLeft(cm, val, isScroller) {
if (isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) return;
val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth);
@@ -1990,7 +2825,7 @@
// know one. These don't have to be accurate -- the result of them
// being wrong would just be a slight flicker on the first wheel
// scroll (if it is large enough).
- if (old_ie) wheelPixelsPerUnit = -.53;
+ if (ie) wheelPixelsPerUnit = -.53;
else if (gecko) wheelPixelsPerUnit = 15;
else if (chrome) wheelPixelsPerUnit = -.7;
else if (safari) wheelPixelsPerUnit = -1/3;
@@ -2011,10 +2846,12 @@
// This hack (see related code in patchDisplay) makes sure the
// element is kept around.
if (dy && mac && webkit) {
- for (var cur = e.target; cur != scroll; cur = cur.parentNode) {
- if (cur.lineObj) {
- cm.display.currentWheelTarget = cur;
- break;
+ outer: for (var cur = e.target, view = display.view; cur != scroll; cur = cur.parentNode) {
+ for (var i = 0; i < view.length; i++) {
+ if (view[i].node == cur) {
+ cm.display.currentWheelTarget = cur;
+ break outer;
+ }
}
}
}
@@ -2025,7 +2862,7 @@
// estimated pixels/delta value, we just handle horizontal
// scrolling entirely here. It'll be slightly off from native, but
// better than glitching out.
- if (dx && !gecko && !opera && wheelPixelsPerUnit != null) {
+ if (dx && !gecko && !presto && wheelPixelsPerUnit != null) {
if (dy)
setScrollTop(cm, Math.max(0, Math.min(scroll.scrollTop + dy * wheelPixelsPerUnit, scroll.scrollHeight - scroll.clientHeight)));
setScrollLeft(cm, Math.max(0, Math.min(scroll.scrollLeft + dx * wheelPixelsPerUnit, scroll.scrollWidth - scroll.clientWidth)));
@@ -2034,12 +2871,14 @@
return;
}
+ // 'Project' the visible viewport to cover the area that is being
+ // scrolled into view (if we know enough to estimate it).
if (dy && wheelPixelsPerUnit != null) {
var pixels = dy * wheelPixelsPerUnit;
var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight;
if (pixels < 0) top = Math.max(0, top + pixels - 50);
else bot = Math.min(cm.doc.height, bot + pixels + 50);
- updateDisplay(cm, [], {top: top, bottom: bot});
+ updateDisplay(cm, {top: top, bottom: bot});
}
if (wheelSamples < 20) {
@@ -2063,6 +2902,9 @@
}
}
+ // KEY EVENTS
+
+ // Run a handler that was bound to a key.
function doHandleBinding(cm, bound, dropShift) {
if (typeof bound == "string") {
bound = commands[bound];
@@ -2071,18 +2913,19 @@
// Ensure previous input has been read, so that the handler sees a
// consistent view of the document
if (cm.display.pollingFast && readInput(cm)) cm.display.pollingFast = false;
- var doc = cm.doc, prevShift = doc.sel.shift, done = false;
+ var prevShift = cm.display.shift, done = false;
try {
if (isReadOnly(cm)) cm.state.suppressEdits = true;
- if (dropShift) doc.sel.shift = false;
+ if (dropShift) cm.display.shift = false;
done = bound(cm) != Pass;
} finally {
- doc.sel.shift = prevShift;
+ cm.display.shift = prevShift;
cm.state.suppressEdits = false;
}
return done;
}
+ // Collect the currently active keymaps.
function allKeyMaps(cm) {
var maps = cm.state.keyMaps.slice(0);
if (cm.options.extraKeys) maps.push(cm.options.extraKeys);
@@ -2091,8 +2934,9 @@
}
var maybeTransition;
+ // Handle a key from the keydown event.
function handleKeyBinding(cm, e) {
- // Handle auto keymap transitions
+ // Handle automatic keymap transitions
var startMap = getKeyMap(cm.options.keyMap), next = startMap.auto;
clearTimeout(maybeTransition);
if (next && !isModifierKey(e)) maybeTransition = setTimeout(function() {
@@ -2122,12 +2966,12 @@
if (handled) {
e_preventDefault(e);
restartBlink(cm);
- if (ie_lt9) { e.oldKeyCode = e.keyCode; e.keyCode = 0; }
signalLater(cm, "keyHandled", cm, name, e);
}
return handled;
}
+ // Handle a key from the keypress event
function handleCharBinding(cm, e, ch) {
var handled = lookupKey("'" + ch + "'", allKeyMaps(cm),
function(b) { return doHandleBinding(cm, b, true); });
@@ -2139,43 +2983,43 @@
return handled;
}
- function onKeyUp(e) {
- var cm = this;
- if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
- if (e.keyCode == 16) cm.doc.sel.shift = false;
- }
-
var lastStoppedKey = null;
function onKeyDown(e) {
var cm = this;
- if (!cm.state.focused) onFocus(cm);
- if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
- if (old_ie && e.keyCode == 27) e.returnValue = false;
- var code = e.keyCode;
+ ensureFocus(cm);
+ if (signalDOMEvent(cm, e)) return;
// IE does strange things with escape.
- cm.doc.sel.shift = code == 16 || e.shiftKey;
- // First give onKeyEvent option a chance to handle this.
+ if (ie_upto10 && e.keyCode == 27) e.returnValue = false;
+ var code = e.keyCode;
+ cm.display.shift = code == 16 || e.shiftKey;
var handled = handleKeyBinding(cm, e);
- if (opera) {
+ if (presto) {
lastStoppedKey = handled ? code : null;
// Opera has no cut event... we try to at least catch the key combo
if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey))
- cm.replaceSelection("");
+ cm.replaceSelection("", null, "cut");
}
}
+ function onKeyUp(e) {
+ if (signalDOMEvent(this, e)) return;
+ if (e.keyCode == 16) this.doc.sel.shift = false;
+ }
+
function onKeyPress(e) {
var cm = this;
- if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
+ if (signalDOMEvent(cm, e)) return;
var keyCode = e.keyCode, charCode = e.charCode;
- if (opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;}
- if (((opera && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(cm, e)) return;
+ if (presto && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;}
+ if (((presto && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(cm, e)) return;
var ch = String.fromCharCode(charCode == null ? keyCode : charCode);
if (handleCharBinding(cm, e, ch)) return;
- if (ie && !ie_lt9) cm.display.inputHasSelection = null;
+ if (ie && !ie_upto8) cm.display.inputHasSelection = null;
fastPoll(cm);
}
+ // FOCUS/BLUR EVENTS
+
function onFocus(cm) {
if (cm.options.readOnly == "nocursor") return;
if (!cm.state.focused) {
@@ -2184,7 +3028,7 @@
if (cm.display.wrapper.className.search(/\bCodeMirror-focused\b/) == -1)
cm.display.wrapper.className += " CodeMirror-focused";
if (!cm.curOp) {
- resetInput(cm, true);
+ resetInput(cm);
if (webkit) setTimeout(bind(resetInput, cm, true), 0); // Issue #1730
}
}
@@ -2198,37 +3042,46 @@
cm.display.wrapper.className = cm.display.wrapper.className.replace(" CodeMirror-focused", "");
}
clearInterval(cm.display.blinker);
- setTimeout(function() {if (!cm.state.focused) cm.doc.sel.shift = false;}, 150);
+ setTimeout(function() {if (!cm.state.focused) cm.display.shift = false;}, 150);
}
+ // CONTEXT MENU HANDLING
+
var detectingSelectAll;
+ // To make the context menu work, we need to briefly unhide the
+ // textarea (making it as unobtrusive as possible) to let the
+ // right-click take effect on it.
function onContextMenu(cm, e) {
if (signalDOMEvent(cm, e, "contextmenu")) return;
- var display = cm.display, sel = cm.doc.sel;
+ var display = cm.display;
if (eventInWidget(display, e) || contextMenuInGutter(cm, e)) return;
var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop;
- if (!pos || opera) return; // Opera is difficult.
+ if (!pos || presto) return; // Opera is difficult.
// Reset the current text selection only if the click is done outside of the selection
// and 'resetSelectionOnContextMenu' option is true.
var reset = cm.options.resetSelectionOnContextMenu;
- if (reset && (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to)))
- operation(cm, setSelection)(cm.doc, pos, pos);
+ if (reset && cm.doc.sel.contains(pos) == -1)
+ operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll);
var oldCSS = display.input.style.cssText;
display.inputDiv.style.position = "absolute";
display.input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) +
- "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: transparent; outline: none;" +
- "border-width: 0; outline: none; overflow: hidden; opacity: .05; -ms-opacity: .05; filter: alpha(opacity=5);";
+ "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: " +
+ (ie ? "rgba(255, 255, 255, .05)" : "transparent") +
+ "; outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);";
focusInput(cm);
- resetInput(cm, true);
+ resetInput(cm);
// Adds "Select all" to context menu in FF
- if (posEq(sel.from, sel.to)) display.input.value = display.prevInput = " ";
+ if (!cm.somethingSelected()) display.input.value = display.prevInput = " ";
+ // Select-all will be greyed out if there's nothing to select, so
+ // this adds a zero-width space so that we can later check whether
+ // it got selected.
function prepareSelectAllHack() {
if (display.input.selectionStart != null) {
- var extval = display.input.value = "\u200b" + (posEq(sel.from, sel.to) ? "" : display.input.value);
+ var extval = display.input.value = "\u200b" + (cm.somethingSelected() ? display.input.value : "");
display.prevInput = "\u200b";
display.input.selectionStart = 1; display.input.selectionEnd = extval.length;
}
@@ -2236,12 +3089,12 @@
function rehide() {
display.inputDiv.style.position = "relative";
display.input.style.cssText = oldCSS;
- if (ie_lt9) display.scrollbarV.scrollTop = display.scroller.scrollTop = scrollPos;
+ if (ie_upto8) display.scrollbarV.scrollTop = display.scroller.scrollTop = scrollPos;
slowPoll(cm);
// Try to detect the user choosing select-all
if (display.input.selectionStart != null) {
- if (!old_ie || ie_lt9) prepareSelectAllHack();
+ if (!ie || ie_upto8) prepareSelectAllHack();
clearTimeout(detectingSelectAll);
var i = 0, poll = function(){
if (display.prevInput == "\u200b" && display.input.selectionStart == 0)
@@ -2253,8 +3106,8 @@
}
}
- if (old_ie && !ie_lt9) prepareSelectAllHack();
- if (captureMiddleClick) {
+ if (ie && !ie_upto8) prepareSelectAllHack();
+ if (captureRightClick) {
e_stop(e);
var mouseup = function() {
off(window, "mouseup", mouseup);
@@ -2266,54 +3119,71 @@
}
}
+ function contextMenuInGutter(cm, e) {
+ if (!hasHandler(cm, "gutterContextMenu")) return false;
+ return gutterEvent(cm, e, "gutterContextMenu", false, signal);
+ }
+
// UPDATING
+ // Compute the position of the end of a change (its 'to' property
+ // refers to the pre-change end).
var changeEnd = CodeMirror.changeEnd = function(change) {
if (!change.text) return change.to;
return Pos(change.from.line + change.text.length - 1,
lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0));
};
- // Make sure a position will be valid after the given change.
- function clipPostChange(doc, change, pos) {
- if (!posLess(change.from, pos)) return clipPos(doc, pos);
- var diff = (change.text.length - 1) - (change.to.line - change.from.line);
- if (pos.line > change.to.line + diff) {
- var preLine = pos.line - diff, lastLine = doc.first + doc.size - 1;
- if (preLine > lastLine) return Pos(lastLine, getLine(doc, lastLine).text.length);
- return clipToLen(pos, getLine(doc, preLine).text.length);
+ // Adjust a position to refer to the post-change position of the
+ // same text, or the end of the change if the change covers it.
+ function adjustForChange(pos, change) {
+ if (cmp(pos, change.from) < 0) return pos;
+ if (cmp(pos, change.to) <= 0) return changeEnd(change);
+
+ var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch;
+ if (pos.line == change.to.line) ch += changeEnd(change).ch - change.to.ch;
+ return Pos(line, ch);
+ }
+
+ function computeSelAfterChange(doc, change) {
+ var out = [];
+ for (var i = 0; i < doc.sel.ranges.length; i++) {
+ var range = doc.sel.ranges[i];
+ out.push(new Range(adjustForChange(range.anchor, change),
+ adjustForChange(range.head, change)));
}
- if (pos.line == change.to.line + diff)
- return clipToLen(pos, lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0) +
- getLine(doc, change.to.line).text.length - change.to.ch);
- var inside = pos.line - change.from.line;
- return clipToLen(pos, change.text[inside].length + (inside ? 0 : change.from.ch));
+ return normalizeSelection(out, doc.sel.primIndex);
}
- // Hint can be null|"end"|"start"|"around"|{anchor,head}
- function computeSelAfterChange(doc, change, hint) {
- if (hint && typeof hint == "object") // Assumed to be {anchor, head} object
- return {anchor: clipPostChange(doc, change, hint.anchor),
- head: clipPostChange(doc, change, hint.head)};
-
- if (hint == "start") return {anchor: change.from, head: change.from};
-
- var end = changeEnd(change);
- if (hint == "around") return {anchor: change.from, head: end};
- if (hint == "end") return {anchor: end, head: end};
-
- // hint is null, leave the selection alone as much as possible
- var adjustPos = function(pos) {
- if (posLess(pos, change.from)) return pos;
- if (!posLess(change.to, pos)) return end;
-
- var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch;
- if (pos.line == change.to.line) ch += end.ch - change.to.ch;
- return Pos(line, ch);
- };
- return {anchor: adjustPos(doc.sel.anchor), head: adjustPos(doc.sel.head)};
+ function offsetPos(pos, old, nw) {
+ if (pos.line == old.line)
+ return Pos(nw.line, pos.ch - old.ch + nw.ch);
+ else
+ return Pos(nw.line + (pos.line - old.line), pos.ch);
}
+ // Used by replaceSelections to allow moving the selection to the
+ // start or around the replaced test. Hint may be "start" or "around".
+ function computeReplacedSel(doc, changes, hint) {
+ var out = [];
+ var oldPrev = Pos(doc.first, 0), newPrev = oldPrev;
+ for (var i = 0; i < changes.length; i++) {
+ var change = changes[i];
+ var from = offsetPos(change.from, oldPrev, newPrev);
+ var to = offsetPos(changeEnd(change), oldPrev, newPrev);
+ oldPrev = change.to;
+ newPrev = to;
+ if (hint == "around") {
+ var range = doc.sel.ranges[i], inv = cmp(range.head, range.anchor) < 0;
+ out[i] = new Range(inv ? to : from, inv ? from : to);
+ } else {
+ out[i] = new Range(from, from);
+ }
+ }
+ return new Selection(out, doc.sel.primIndex);
+ }
+
+ // Allow "beforeChange" event handlers to influence a change
function filterChange(doc, change, update) {
var obj = {
canceled: false,
@@ -2336,11 +3206,11 @@
return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin};
}
- // Replace the range from from to to by the strings in replacement.
- // change is a {from, to, text [, origin]} object
- function makeChange(doc, change, selUpdate, ignoreReadOnly) {
+ // Apply a change to a document, and add it to the document's
+ // history, and propagating it to all linked documents.
+ function makeChange(doc, change, ignoreReadOnly) {
if (doc.cm) {
- if (!doc.cm.curOp) return operation(doc.cm, makeChange)(doc, change, selUpdate, ignoreReadOnly);
+ if (!doc.cm.curOp) return operation(doc.cm, makeChange)(doc, change, ignoreReadOnly);
if (doc.cm.state.suppressEdits) return;
}
@@ -2353,19 +3223,17 @@
// of read-only spans in its range.
var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to);
if (split) {
- for (var i = split.length - 1; i >= 1; --i)
- makeChangeNoReadonly(doc, {from: split[i].from, to: split[i].to, text: [""]});
- if (split.length)
- makeChangeNoReadonly(doc, {from: split[0].from, to: split[0].to, text: change.text}, selUpdate);
+ for (var i = split.length - 1; i >= 0; --i)
+ makeChangeInner(doc, {from: split[i].from, to: split[i].to, text: i ? [""] : change.text});
} else {
- makeChangeNoReadonly(doc, change, selUpdate);
+ makeChangeInner(doc, change);
}
}
- function makeChangeNoReadonly(doc, change, selUpdate) {
- if (change.text.length == 1 && change.text[0] == "" && posEq(change.from, change.to)) return;
- var selAfter = computeSelAfterChange(doc, change, selUpdate);
- addToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN);
+ function makeChangeInner(doc, change) {
+ if (change.text.length == 1 && change.text[0] == "" && cmp(change.from, change.to) == 0) return;
+ var selAfter = computeSelAfterChange(doc, change);
+ addChangeToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN);
makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change));
var rebased = [];
@@ -2379,17 +3247,41 @@
});
}
- function makeChangeFromHistory(doc, type) {
+ // Revert a change stored in a document's history.
+ function makeChangeFromHistory(doc, type, allowSelectionOnly) {
if (doc.cm && doc.cm.state.suppressEdits) return;
- var hist = doc.history;
- var event = (type == "undo" ? hist.done : hist.undone).pop();
- if (!event) return;
+ var hist = doc.history, event, selAfter = doc.sel;
+ var source = type == "undo" ? hist.done : hist.undone, dest = type == "undo" ? hist.undone : hist.done;
- var anti = {changes: [], anchorBefore: event.anchorAfter, headBefore: event.headAfter,
- anchorAfter: event.anchorBefore, headAfter: event.headBefore,
- generation: hist.generation};
- (type == "undo" ? hist.undone : hist.done).push(anti);
+ // Verify that there is a useable event (so that ctrl-z won't
+ // needlessly clear selection events)
+ for (var i = 0; i < source.length; i++) {
+ event = source[i];
+ if (allowSelectionOnly ? event.ranges && !event.equals(doc.sel) : !event.ranges)
+ break;
+ }
+ if (i == source.length) return;
+ hist.lastOrigin = hist.lastSelOrigin = null;
+
+ for (;;) {
+ event = source.pop();
+ if (event.ranges) {
+ pushSelectionToHistory(event, dest);
+ if (allowSelectionOnly && !event.equals(doc.sel)) {
+ setSelection(doc, event, {clearRedo: false});
+ return;
+ }
+ selAfter = event;
+ }
+ else break;
+ }
+
+ // Build up a reverse change object to add to the opposite history
+ // stack (redo when undoing, and vice versa).
+ var antiChanges = [];
+ pushSelectionToHistory(selAfter, dest);
+ dest.push({changes: antiChanges, generation: hist.generation});
hist.generation = event.generation || ++hist.maxGeneration;
var filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange");
@@ -2398,17 +3290,18 @@
var change = event.changes[i];
change.origin = type;
if (filter && !filterChange(doc, change, false)) {
- (type == "undo" ? hist.done : hist.undone).length = 0;
+ source.length = 0;
return;
}
- anti.changes.push(historyChangeFromChange(doc, change));
+ antiChanges.push(historyChangeFromChange(doc, change));
- var after = i ? computeSelAfterChange(doc, change, null)
- : {anchor: event.anchorBefore, head: event.headBefore};
+ var after = i ? computeSelAfterChange(doc, change, null) : lst(source);
makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change));
+ if (doc.cm) ensureCursorVisible(doc.cm);
var rebased = [];
+ // Propagate to the linked documents
linkedDocs(doc, function(doc, sharedHist) {
if (!sharedHist && indexOf(rebased, doc.history) == -1) {
rebaseHist(doc.history, change);
@@ -2419,14 +3312,19 @@
}
}
+ // Sub-views need their line numbers shifted when text is added
+ // above or below them in the parent document.
function shiftDoc(doc, distance) {
- function shiftPos(pos) {return Pos(pos.line + distance, pos.ch);}
doc.first += distance;
- if (doc.cm) regChange(doc.cm, doc.first, doc.first, distance);
- doc.sel.head = shiftPos(doc.sel.head); doc.sel.anchor = shiftPos(doc.sel.anchor);
- doc.sel.from = shiftPos(doc.sel.from); doc.sel.to = shiftPos(doc.sel.to);
+ doc.sel = new Selection(map(doc.sel.ranges, function(range) {
+ return new Range(Pos(range.anchor.line + distance, range.anchor.ch),
+ Pos(range.head.line + distance, range.head.ch));
+ }), doc.sel.primIndex);
+ if (doc.cm) regChange(doc.cm, doc.first, doc.first - distance, distance);
}
+ // More lower-level change function, handling only a single document
+ // (not linked ones).
function makeChangeSingleDoc(doc, change, selAfter, spans) {
if (doc.cm && !doc.cm.curOp)
return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans);
@@ -2453,16 +3351,19 @@
change.removed = getBetween(doc, change.from, change.to);
if (!selAfter) selAfter = computeSelAfterChange(doc, change, null);
- if (doc.cm) makeChangeSingleDocInEditor(doc.cm, change, spans, selAfter);
- else updateDoc(doc, change, spans, selAfter);
+ if (doc.cm) makeChangeSingleDocInEditor(doc.cm, change, spans);
+ else updateDoc(doc, change, spans);
+ setSelectionNoUndo(doc, selAfter, sel_dontScroll);
}
- function makeChangeSingleDocInEditor(cm, change, spans, selAfter) {
+ // Handle the interaction of a change to a document with the editor
+ // that this document is part of.
+ function makeChangeSingleDocInEditor(cm, change, spans) {
var doc = cm.doc, display = cm.display, from = change.from, to = change.to;
var recomputeMaxLength = false, checkWidthStart = from.line;
if (!cm.options.lineWrapping) {
- checkWidthStart = lineNo(visualLine(doc, getLine(doc, from.line)));
+ checkWidthStart = lineNo(visualLine(getLine(doc, from.line)));
doc.iter(checkWidthStart, to.line + 1, function(line) {
if (line == display.maxLine) {
recomputeMaxLength = true;
@@ -2471,14 +3372,14 @@
});
}
- if (!posLess(doc.sel.head, change.from) && !posLess(change.to, doc.sel.head))
+ if (doc.sel.contains(change.from, change.to) > -1)
cm.curOp.cursorActivity = true;
- updateDoc(doc, change, spans, selAfter, estimateHeight(cm));
+ updateDoc(doc, change, spans, estimateHeight(cm));
if (!cm.options.lineWrapping) {
doc.iter(checkWidthStart, from.line + change.text.length, function(line) {
- var len = lineLength(doc, line);
+ var len = lineLength(line);
if (len > display.maxLineLength) {
display.maxLine = line;
display.maxLineLength = len;
@@ -2495,184 +3396,38 @@
var lendiff = change.text.length - (to.line - from.line) - 1;
// Remember that these lines changed, for updating the display
- regChange(cm, from.line, to.line + 1, lendiff);
+ if (from.line == to.line && change.text.length == 1 && !isWholeLineUpdate(cm.doc, change))
+ regLineChange(cm, from.line, "text");
+ else
+ regChange(cm, from.line, to.line + 1, lendiff);
- if (hasHandler(cm, "change")) {
- var changeObj = {from: from, to: to,
- text: change.text,
- removed: change.removed,
- origin: change.origin};
- if (cm.curOp.textChanged) {
- for (var cur = cm.curOp.textChanged; cur.next; cur = cur.next) {}
- cur.next = changeObj;
- } else cm.curOp.textChanged = changeObj;
- }
+ if (hasHandler(cm, "change") || hasHandler(cm, "changes"))
+ (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).push({
+ from: from, to: to,
+ text: change.text,
+ removed: change.removed,
+ origin: change.origin
+ });
}
function replaceRange(doc, code, from, to, origin) {
if (!to) to = from;
- if (posLess(to, from)) { var tmp = to; to = from; from = tmp; }
+ if (cmp(to, from) < 0) { var tmp = to; to = from; from = tmp; }
if (typeof code == "string") code = splitLines(code);
- makeChange(doc, {from: from, to: to, text: code, origin: origin}, null);
+ makeChange(doc, {from: from, to: to, text: code, origin: origin});
}
- // POSITION OBJECT
+ // SCROLLING THINGS INTO VIEW
- function Pos(line, ch) {
- if (!(this instanceof Pos)) return new Pos(line, ch);
- this.line = line; this.ch = ch;
- }
- CodeMirror.Pos = Pos;
-
- function posEq(a, b) {return a.line == b.line && a.ch == b.ch;}
- function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);}
- function cmp(a, b) {return a.line - b.line || a.ch - b.ch;}
- function copyPos(x) {return Pos(x.line, x.ch);}
-
- // SELECTION
-
- function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1));}
- function clipPos(doc, pos) {
- if (pos.line < doc.first) return Pos(doc.first, 0);
- var last = doc.first + doc.size - 1;
- if (pos.line > last) return Pos(last, getLine(doc, last).text.length);
- return clipToLen(pos, getLine(doc, pos.line).text.length);
- }
- function clipToLen(pos, linelen) {
- var ch = pos.ch;
- if (ch == null || ch > linelen) return Pos(pos.line, linelen);
- else if (ch < 0) return Pos(pos.line, 0);
- else return pos;
- }
- function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size;}
-
- // If shift is held, this will move the selection anchor. Otherwise,
- // it'll set the whole selection.
- function extendSelection(doc, pos, other, bias) {
- if (doc.sel.shift || doc.sel.extend) {
- var anchor = doc.sel.anchor;
- if (other) {
- var posBefore = posLess(pos, anchor);
- if (posBefore != posLess(other, anchor)) {
- anchor = pos;
- pos = other;
- } else if (posBefore != posLess(pos, other)) {
- pos = other;
- }
- }
- setSelection(doc, anchor, pos, bias);
- } else {
- setSelection(doc, pos, other || pos, bias);
- }
- if (doc.cm) doc.cm.curOp.userSelChange = true;
- }
-
- function filterSelectionChange(doc, anchor, head) {
- var obj = {anchor: anchor, head: head};
- signal(doc, "beforeSelectionChange", doc, obj);
- if (doc.cm) signal(doc.cm, "beforeSelectionChange", doc.cm, obj);
- obj.anchor = clipPos(doc, obj.anchor); obj.head = clipPos(doc, obj.head);
- return obj;
- }
-
- // Update the selection. Last two args are only used by
- // updateDoc, since they have to be expressed in the line
- // numbers before the update.
- function setSelection(doc, anchor, head, bias, checkAtomic) {
- if (!checkAtomic && hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange")) {
- var filtered = filterSelectionChange(doc, anchor, head);
- head = filtered.head;
- anchor = filtered.anchor;
- }
-
- var sel = doc.sel;
- sel.goalColumn = null;
- if (bias == null) bias = posLess(head, sel.head) ? -1 : 1;
- // Skip over atomic spans.
- if (checkAtomic || !posEq(anchor, sel.anchor))
- anchor = skipAtomic(doc, anchor, bias, checkAtomic != "push");
- if (checkAtomic || !posEq(head, sel.head))
- head = skipAtomic(doc, head, bias, checkAtomic != "push");
-
- if (posEq(sel.anchor, anchor) && posEq(sel.head, head)) return;
-
- sel.anchor = anchor; sel.head = head;
- var inv = posLess(head, anchor);
- sel.from = inv ? head : anchor;
- sel.to = inv ? anchor : head;
-
- if (doc.cm)
- doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged =
- doc.cm.curOp.cursorActivity = true;
-
- signalLater(doc, "cursorActivity", doc);
- }
-
- function reCheckSelection(cm) {
- setSelection(cm.doc, cm.doc.sel.from, cm.doc.sel.to, null, "push");
- }
-
- function skipAtomic(doc, pos, bias, mayClear) {
- var flipped = false, curPos = pos;
- var dir = bias || 1;
- doc.cantEdit = false;
- search: for (;;) {
- var line = getLine(doc, curPos.line);
- if (line.markedSpans) {
- for (var i = 0; i < line.markedSpans.length; ++i) {
- var sp = line.markedSpans[i], m = sp.marker;
- if ((sp.from == null || (m.inclusiveLeft ? sp.from <= curPos.ch : sp.from < curPos.ch)) &&
- (sp.to == null || (m.inclusiveRight ? sp.to >= curPos.ch : sp.to > curPos.ch))) {
- if (mayClear) {
- signal(m, "beforeCursorEnter");
- if (m.explicitlyCleared) {
- if (!line.markedSpans) break;
- else {--i; continue;}
- }
- }
- if (!m.atomic) continue;
- var newPos = m.find()[dir < 0 ? "from" : "to"];
- if (posEq(newPos, curPos)) {
- newPos.ch += dir;
- if (newPos.ch < 0) {
- if (newPos.line > doc.first) newPos = clipPos(doc, Pos(newPos.line - 1));
- else newPos = null;
- } else if (newPos.ch > line.text.length) {
- if (newPos.line < doc.first + doc.size - 1) newPos = Pos(newPos.line + 1, 0);
- else newPos = null;
- }
- if (!newPos) {
- if (flipped) {
- // Driven in a corner -- no valid cursor position found at all
- // -- try again *with* clearing, if we didn't already
- if (!mayClear) return skipAtomic(doc, pos, bias, true);
- // Otherwise, turn off editing until further notice, and return the start of the doc
- doc.cantEdit = true;
- return Pos(doc.first, 0);
- }
- flipped = true; newPos = pos; dir = -dir;
- }
- }
- curPos = newPos;
- continue search;
- }
- }
- }
- return curPos;
- }
- }
-
- // SCROLLING
-
- function scrollCursorIntoView(cm) {
- var coords = scrollPosIntoView(cm, cm.doc.sel.head, null, cm.options.cursorScrollMargin);
- if (!cm.state.focused) return;
- var display = cm.display, box = getRect(display.sizer), doScroll = null;
+ // If an editor sits on the top or bottom of the window, partially
+ // scrolled out of view, this ensures that the cursor is visible.
+ function maybeScrollWindow(cm, coords) {
+ var display = cm.display, box = display.sizer.getBoundingClientRect(), doScroll = null;
if (coords.top + box.top < 0) doScroll = true;
else if (coords.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false;
if (doScroll != null && !phantom) {
var scrollNode = elt("div", "\u200b", null, "position: absolute; top: " +
- (coords.top - display.viewOffset) + "px; height: " +
+ (coords.top - display.viewOffset - paddingTop(cm.display)) + "px; height: " +
(coords.bottom - coords.top + scrollerCutOff) + "px; left: " +
coords.left + "px; width: 2px;");
cm.display.lineSpace.appendChild(scrollNode);
@@ -2681,6 +3436,9 @@
}
}
+ // Scroll a given position into view (immediately), verifying that
+ // it actually became visible (as line heights are accurately
+ // measured, the position of something may 'drift' during drawing).
function scrollPosIntoView(cm, pos, end, margin) {
if (margin == null) margin = 0;
for (;;) {
@@ -2703,16 +3461,22 @@
}
}
+ // Scroll a given set of coordinates into view (immediately).
function scrollIntoView(cm, x1, y1, x2, y2) {
var scrollPos = calculateScrollPos(cm, x1, y1, x2, y2);
if (scrollPos.scrollTop != null) setScrollTop(cm, scrollPos.scrollTop);
if (scrollPos.scrollLeft != null) setScrollLeft(cm, scrollPos.scrollLeft);
}
+ // Calculate a new scroll position needed to scroll the given
+ // rectangle into view. Returns an object with scrollTop and
+ // scrollLeft properties. When these are undefined, the
+ // vertical/horizontal position does not need to be adjusted.
function calculateScrollPos(cm, x1, y1, x2, y2) {
var display = cm.display, snapMargin = textHeight(cm.display);
if (y1 < 0) y1 = 0;
- var screen = display.scroller.clientHeight - scrollerCutOff, screentop = display.scroller.scrollTop, result = {};
+ var screentop = cm.curOp && cm.curOp.scrollTop != null ? cm.curOp.scrollTop : display.scroller.scrollTop;
+ var screen = display.scroller.clientHeight - scrollerCutOff, result = {};
var docBottom = cm.doc.height + paddingVert(display);
var atTop = y1 < snapMargin, atBottom = y2 > docBottom - snapMargin;
if (y1 < screentop) {
@@ -2722,7 +3486,8 @@
if (newTop != screentop) result.scrollTop = newTop;
}
- var screenw = display.scroller.clientWidth - scrollerCutOff, screenleft = display.scroller.scrollLeft;
+ var screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft : display.scroller.scrollLeft;
+ var screenw = display.scroller.clientWidth - scrollerCutOff;
x1 += display.gutters.offsetWidth; x2 += display.gutters.offsetWidth;
var gutterw = display.gutters.offsetWidth;
var atLeft = x1 < gutterw + 10;
@@ -2735,30 +3500,65 @@
return result;
}
- function updateScrollPos(cm, left, top) {
- cm.curOp.updateScrollPos = {scrollLeft: left == null ? cm.doc.scrollLeft : left,
- scrollTop: top == null ? cm.doc.scrollTop : top};
+ // Store a relative adjustment to the scroll position in the current
+ // operation (to be applied when the operation finishes).
+ function addToScrollPos(cm, left, top) {
+ if (left != null || top != null) resolveScrollToPos(cm);
+ if (left != null)
+ cm.curOp.scrollLeft = (cm.curOp.scrollLeft == null ? cm.doc.scrollLeft : cm.curOp.scrollLeft) + left;
+ if (top != null)
+ cm.curOp.scrollTop = (cm.curOp.scrollTop == null ? cm.doc.scrollTop : cm.curOp.scrollTop) + top;
}
- function addToScrollPos(cm, left, top) {
- var pos = cm.curOp.updateScrollPos || (cm.curOp.updateScrollPos = {scrollLeft: cm.doc.scrollLeft, scrollTop: cm.doc.scrollTop});
- var scroll = cm.display.scroller;
- pos.scrollTop = Math.max(0, Math.min(scroll.scrollHeight - scroll.clientHeight, pos.scrollTop + top));
- pos.scrollLeft = Math.max(0, Math.min(scroll.scrollWidth - scroll.clientWidth, pos.scrollLeft + left));
+ // Make sure that at the end of the operation the current cursor is
+ // shown.
+ function ensureCursorVisible(cm) {
+ resolveScrollToPos(cm);
+ var cur = cm.getCursor(), from = cur, to = cur;
+ if (!cm.options.lineWrapping) {
+ from = cur.ch ? Pos(cur.line, cur.ch - 1) : cur;
+ to = Pos(cur.line, cur.ch + 1);
+ }
+ cm.curOp.scrollToPos = {from: from, to: to, margin: cm.options.cursorScrollMargin, isCursor: true};
+ }
+
+ // When an operation has its scrollToPos property set, and another
+ // scroll action is applied before the end of the operation, this
+ // 'simulates' scrolling that position into view in a cheap way, so
+ // that the effect of intermediate scroll commands is not ignored.
+ function resolveScrollToPos(cm) {
+ var range = cm.curOp.scrollToPos;
+ if (range) {
+ cm.curOp.scrollToPos = null;
+ var from = estimateCoords(cm, range.from), to = estimateCoords(cm, range.to);
+ var sPos = calculateScrollPos(cm, Math.min(from.left, to.left),
+ Math.min(from.top, to.top) - range.margin,
+ Math.max(from.right, to.right),
+ Math.max(from.bottom, to.bottom) + range.margin);
+ cm.scrollTo(sPos.scrollLeft, sPos.scrollTop);
+ }
}
// API UTILITIES
+ // Indent the given line. The how parameter can be "smart",
+ // "add"/null, "subtract", or "prev". When aggressive is false
+ // (typically set to true for forced single-line indents), empty
+ // lines are not indented, and places where the mode returns Pass
+ // are left alone.
function indentLine(cm, n, how, aggressive) {
- var doc = cm.doc;
+ var doc = cm.doc, state;
if (how == null) how = "add";
if (how == "smart") {
+ // Fall back to "prev" when the mode doesn't have an indentation
+ // method.
if (!cm.doc.mode.indent) how = "prev";
- else var state = getStateBefore(cm, n);
+ else state = getStateBefore(cm, n);
}
var tabSize = cm.options.tabSize;
var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize);
+ if (line.stateAfter) line.stateAfter = null;
var curSpaceString = line.text.match(/^\s*/)[0], indentation;
if (!aggressive && !/\S/.test(line.text)) {
indentation = 0;
@@ -2787,23 +3587,70 @@
for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t";}
if (pos < indentation) indentString += spaceStr(indentation - pos);
- if (indentString != curSpaceString)
+ if (indentString != curSpaceString) {
replaceRange(cm.doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input");
- else if (doc.sel.head.line == n && doc.sel.head.ch < curSpaceString.length)
- setSelection(doc, Pos(n, curSpaceString.length), Pos(n, curSpaceString.length), 1);
+ } else {
+ // Ensure that, if the cursor was in the whitespace at the start
+ // of the line, it is moved to the end of that space.
+ for (var i = 0; i < doc.sel.ranges.length; i++) {
+ var range = doc.sel.ranges[i];
+ if (range.head.line == n && range.head.ch < curSpaceString.length) {
+ var pos = Pos(n, curSpaceString.length);
+ replaceOneSelection(doc, i, new Range(pos, pos));
+ break;
+ }
+ }
+ }
line.stateAfter = null;
}
- function changeLine(cm, handle, op) {
+ // Utility for applying a change to a line by handle or number,
+ // returning the number and optionally registering the line as
+ // changed.
+ function changeLine(cm, handle, changeType, op) {
var no = handle, line = handle, doc = cm.doc;
if (typeof handle == "number") line = getLine(doc, clipLine(doc, handle));
else no = lineNo(handle);
if (no == null) return null;
- if (op(line, no)) regChange(cm, no, no + 1);
+ if (op(line, no)) regLineChange(cm, no, changeType);
else return null;
return line;
}
+ // Helper for deleting text near the selection(s), used to implement
+ // backspace, delete, and similar functionality.
+ function deleteNearSelection(cm, compute) {
+ var ranges = cm.doc.sel.ranges, kill = [];
+ // Build up a set of ranges to kill first, merging overlapping
+ // ranges.
+ for (var i = 0; i < ranges.length; i++) {
+ var toKill = compute(ranges[i]);
+ while (kill.length && cmp(toKill.from, lst(kill).to) <= 0) {
+ var replaced = kill.pop();
+ if (cmp(replaced.from, toKill.from) < 0) {
+ toKill.from = replaced.from;
+ break;
+ }
+ }
+ kill.push(toKill);
+ }
+ // Next, remove those actual ranges.
+ runInOp(cm, function() {
+ for (var i = kill.length - 1; i >= 0; i--)
+ replaceRange(cm.doc, "", kill[i].from, kill[i].to, "+delete");
+ ensureCursorVisible(cm);
+ });
+ }
+
+ // Used for horizontal relative motion. Dir is -1 or 1 (left or
+ // right), unit can be "char", "column" (like char, but doesn't
+ // cross line boundaries), "word" (across next word), or "group" (to
+ // the start of next group of word or non-word-non-whitespace
+ // chars). The visually param controls whether, in right-to-left
+ // text, direction 1 means to move towards the next index in the
+ // string, or towards the character to the right of the current
+ // position. The resulting position will have a hitSide=true
+ // property if it reached the end of the document.
function findPosH(doc, pos, dir, unit, visually) {
var line = pos.line, ch = pos.ch, origDir = dir;
var lineObj = getLine(doc, line);
@@ -2833,13 +3680,15 @@
if (dir < 0 && !moveOnce(!first)) break;
var cur = lineObj.text.charAt(ch) || "\n";
var type = isWordChar(cur) ? "w"
- : !group ? null
- : /\s/.test(cur) ? null
+ : group && cur == "\n" ? "n"
+ : !group || /\s/.test(cur) ? null
: "p";
+ if (group && !first && !type) type = "s";
if (sawType && sawType != type) {
if (dir < 0) {dir = 1; moveOnce();}
break;
}
+
if (type) sawType = type;
if (dir > 0 && !moveOnce(!first)) break;
}
@@ -2849,6 +3698,9 @@
return result;
}
+ // For relative vertical movement. Dir may be -1 or 1. Unit can be
+ // "page" or "line". The resulting position will have a hitSide=true
+ // property if it reached the end of the document.
function findPosV(cm, pos, dir, unit) {
var doc = cm.doc, x = pos.left, y;
if (unit == "page") {
@@ -2866,7 +3718,9 @@
return target;
}
- function findWordAt(line, pos) {
+ // Find the word at the given position (as returned by coordsChar).
+ function findWordAt(doc, pos) {
+ var line = getLine(doc, pos.line).text;
var start = pos.ch, end = pos.ch;
if (line) {
if ((pos.xRel < 0 || end == line.length) && start) --start; else ++end;
@@ -2877,17 +3731,18 @@
while (start > 0 && check(line.charAt(start - 1))) --start;
while (end < line.length && check(line.charAt(end))) ++end;
}
- return {from: Pos(pos.line, start), to: Pos(pos.line, end)};
+ return new Range(Pos(pos.line, start), Pos(pos.line, end));
}
- function selectLine(cm, line) {
- extendSelection(cm.doc, Pos(line, 0), clipPos(cm.doc, Pos(line + 1, 0)));
- }
+ // EDITOR METHODS
- // PROTOTYPE
+ // The publicly visible API. Note that methodOp(f) means
+ // 'wrap f in an operation, performed on its `this` parameter'.
- // The publicly visible API. Note that operation(null, f) means
- // 'wrap f in an operation, performed on its `this` parameter'
+ // This is not the complete set of editor methods. Most of the
+ // methods defined on the Doc type are also injected into
+ // CodeMirror.prototype, for backwards compatibility and
+ // convenience.
CodeMirror.prototype = {
constructor: CodeMirror,
@@ -2916,14 +3771,14 @@
}
},
- addOverlay: operation(null, function(spec, options) {
+ addOverlay: methodOp(function(spec, options) {
var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec);
if (mode.startState) throw new Error("Overlays may not be stateful.");
this.state.overlays.push({mode: mode, modeSpec: spec, opaque: options && options.opaque});
this.state.modeGen++;
regChange(this);
}),
- removeOverlay: operation(null, function(spec) {
+ removeOverlay: methodOp(function(spec) {
var overlays = this.state.overlays;
for (var i = 0; i < overlays.length; ++i) {
var cur = overlays[i].modeSpec;
@@ -2936,18 +3791,29 @@
}
}),
- indentLine: operation(null, function(n, dir, aggressive) {
+ indentLine: methodOp(function(n, dir, aggressive) {
if (typeof dir != "string" && typeof dir != "number") {
if (dir == null) dir = this.options.smartIndent ? "smart" : "prev";
else dir = dir ? "add" : "subtract";
}
if (isLine(this.doc, n)) indentLine(this, n, dir, aggressive);
}),
- indentSelection: operation(null, function(how) {
- var sel = this.doc.sel;
- if (posEq(sel.from, sel.to)) return indentLine(this, sel.from.line, how, true);
- var e = sel.to.line - (sel.to.ch ? 0 : 1);
- for (var i = sel.from.line; i <= e; ++i) indentLine(this, i, how);
+ indentSelection: methodOp(function(how) {
+ var ranges = this.doc.sel.ranges, end = -1;
+ for (var i = 0; i < ranges.length; i++) {
+ var range = ranges[i];
+ if (!range.empty()) {
+ var start = Math.max(end, range.from().line);
+ var to = range.to();
+ end = Math.min(this.lastLine(), to.line - (to.ch ? 0 : 1)) + 1;
+ for (var j = start; j < end; ++j)
+ indentLine(this, j, how);
+ } else if (range.head.line > end) {
+ indentLine(this, range.head.line, how, true);
+ end = range.head.line;
+ if (i == this.doc.sel.primIndex) ensureCursorVisible(this);
+ }
+ }
}),
// Fetch the parser token for a given character. Useful for hacks
@@ -2965,7 +3831,6 @@
return {start: stream.start,
end: stream.pos,
string: stream.current(),
- className: style || null, // Deprecated, use 'type' instead
type: style || null,
state: state};
},
@@ -3024,10 +3889,10 @@
},
cursorCoords: function(start, mode) {
- var pos, sel = this.doc.sel;
- if (start == null) pos = sel.head;
+ var pos, range = this.doc.sel.primary();
+ if (start == null) pos = range.head;
else if (typeof start == "object") pos = clipPos(this.doc, start);
- else pos = start ? sel.from : sel.to;
+ else pos = start ? range.from() : range.to();
return cursorCoords(this, pos, mode || "page");
},
@@ -3049,15 +3914,15 @@
if (line < this.doc.first) line = this.doc.first;
else if (line > last) { line = last; end = true; }
var lineObj = getLine(this.doc, line);
- return intoCoordSystem(this, getLine(this.doc, line), {top: 0, left: 0}, mode || "page").top +
- (end ? lineObj.height : 0);
+ return intoCoordSystem(this, lineObj, {top: 0, left: 0}, mode || "page").top +
+ (end ? this.doc.height - heightAtLine(lineObj) : 0);
},
defaultTextHeight: function() { return textHeight(this.display); },
defaultCharWidth: function() { return charWidth(this.display); },
- setGutterMarker: operation(null, function(line, gutterID, value) {
- return changeLine(this, line, function(line) {
+ setGutterMarker: methodOp(function(line, gutterID, value) {
+ return changeLine(this, line, "gutter", function(line) {
var markers = line.gutterMarkers || (line.gutterMarkers = {});
markers[gutterID] = value;
if (!value && isEmpty(markers)) line.gutterMarkers = null;
@@ -3065,20 +3930,20 @@
});
}),
- clearGutter: operation(null, function(gutterID) {
+ clearGutter: methodOp(function(gutterID) {
var cm = this, doc = cm.doc, i = doc.first;
doc.iter(function(line) {
if (line.gutterMarkers && line.gutterMarkers[gutterID]) {
line.gutterMarkers[gutterID] = null;
- regChange(cm, i, i + 1);
+ regLineChange(cm, i, "gutter");
if (isEmpty(line.gutterMarkers)) line.gutterMarkers = null;
}
++i;
});
}),
- addLineClass: operation(null, function(handle, where, cls) {
- return changeLine(this, handle, function(line) {
+ addLineClass: methodOp(function(handle, where, cls) {
+ return changeLine(this, handle, "class", function(line) {
var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass";
if (!line[prop]) line[prop] = cls;
else if (new RegExp("(?:^|\\s)" + cls + "(?:$|\\s)").test(line[prop])) return false;
@@ -3087,8 +3952,8 @@
});
}),
- removeLineClass: operation(null, function(handle, where, cls) {
- return changeLine(this, handle, function(line) {
+ removeLineClass: methodOp(function(handle, where, cls) {
+ return changeLine(this, handle, "class", function(line) {
var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass";
var cur = line[prop];
if (!cur) return false;
@@ -3103,7 +3968,7 @@
});
}),
- addLineWidget: operation(null, function(handle, node, options) {
+ addLineWidget: methodOp(function(handle, node, options) {
return addLineWidget(this, handle, node, options);
}),
@@ -3124,7 +3989,7 @@
widgets: line.widgets};
},
- getViewport: function() { return {from: this.display.showingFrom, to: this.display.showingTo};},
+ getViewport: function() { return {from: this.display.viewFrom, to: this.display.viewTo};},
addWidget: function(pos, node, scroll, vert, horiz) {
var display = this.display;
@@ -3159,9 +4024,9 @@
scrollIntoView(this, left, top, left + node.offsetWidth, top + node.offsetHeight);
},
- triggerOnKeyDown: operation(null, onKeyDown),
- triggerOnKeyPress: operation(null, onKeyPress),
- triggerOnKeyUp: operation(null, onKeyUp),
+ triggerOnKeyDown: methodOp(onKeyDown),
+ triggerOnKeyPress: methodOp(onKeyPress),
+ triggerOnKeyUp: methodOp(onKeyUp),
execCommand: function(cmd) {
if (commands.hasOwnProperty(cmd))
@@ -3178,20 +4043,25 @@
return cur;
},
- moveH: operation(null, function(dir, unit) {
- var sel = this.doc.sel, pos;
- if (sel.shift || sel.extend || posEq(sel.from, sel.to))
- pos = findPosH(this.doc, sel.head, dir, unit, this.options.rtlMoveVisually);
- else
- pos = dir < 0 ? sel.from : sel.to;
- extendSelection(this.doc, pos, pos, dir);
+ moveH: methodOp(function(dir, unit) {
+ var cm = this;
+ cm.extendSelectionsBy(function(range) {
+ if (cm.display.shift || cm.doc.extend || range.empty())
+ return findPosH(cm.doc, range.head, dir, unit, cm.options.rtlMoveVisually);
+ else
+ return dir < 0 ? range.from() : range.to();
+ }, sel_move);
}),
- deleteH: operation(null, function(dir, unit) {
- var sel = this.doc.sel;
- if (!posEq(sel.from, sel.to)) replaceRange(this.doc, "", sel.from, sel.to, "+delete");
- else replaceRange(this.doc, "", sel.from, findPosH(this.doc, sel.head, dir, unit, false), "+delete");
- this.curOp.userSelChange = true;
+ deleteH: methodOp(function(dir, unit) {
+ var sel = this.doc.sel, doc = this.doc;
+ if (sel.somethingSelected())
+ doc.replaceSelection("", null, "+delete");
+ else
+ deleteNearSelection(this, function(range) {
+ var other = findPosH(doc, range.head, dir, unit, false);
+ return dir < 0 ? {from: other, to: range.head} : {from: range.head, to: other};
+ });
}),
findPosV: function(from, amount, unit, goalColumn) {
@@ -3207,32 +4077,39 @@
return cur;
},
- moveV: operation(null, function(dir, unit) {
- var sel = this.doc.sel, target, goal;
- if (sel.shift || sel.extend || posEq(sel.from, sel.to)) {
- var pos = cursorCoords(this, sel.head, "div");
- if (sel.goalColumn != null) pos.left = sel.goalColumn;
- target = findPosV(this, pos, dir, unit);
- if (unit == "page") addToScrollPos(this, 0, charCoords(this, target, "div").top - pos.top);
- goal = pos.left;
- } else {
- target = dir < 0 ? sel.from : sel.to;
- }
- extendSelection(this.doc, target, target, dir);
- if (goal != null) sel.goalColumn = goal;
+ moveV: methodOp(function(dir, unit) {
+ var cm = this, doc = this.doc, goals = [];
+ var collapse = !cm.display.shift && !doc.extend && doc.sel.somethingSelected();
+ doc.extendSelectionsBy(function(range) {
+ if (collapse)
+ return dir < 0 ? range.from() : range.to();
+ var headPos = cursorCoords(cm, range.head, "div");
+ if (range.goalColumn != null) headPos.left = range.goalColumn;
+ goals.push(headPos.left);
+ var pos = findPosV(cm, headPos, dir, unit);
+ if (unit == "page" && range == doc.sel.primary())
+ addToScrollPos(cm, null, charCoords(cm, pos, "div").top - headPos.top);
+ return pos;
+ }, sel_move);
+ if (goals.length) for (var i = 0; i < doc.sel.ranges.length; i++)
+ doc.sel.ranges[i].goalColumn = goals[i];
}),
toggleOverwrite: function(value) {
if (value != null && value == this.state.overwrite) return;
if (this.state.overwrite = !this.state.overwrite)
- this.display.cursor.className += " CodeMirror-overwrite";
+ this.display.cursorDiv.className += " CodeMirror-overwrite";
else
- this.display.cursor.className = this.display.cursor.className.replace(" CodeMirror-overwrite", "");
- },
- hasFocus: function() { return document.activeElement == this.display.input; },
+ this.display.cursorDiv.className = this.display.cursorDiv.className.replace(" CodeMirror-overwrite", "");
- scrollTo: operation(null, function(x, y) {
- updateScrollPos(this, x, y);
+ signal(this, "overwriteToggle", this, this.state.overwrite);
+ },
+ hasFocus: function() { return activeElt() == this.display.input; },
+
+ scrollTo: methodOp(function(x, y) {
+ if (x != null || y != null) resolveScrollToPos(this);
+ if (x != null) this.curOp.scrollLeft = x;
+ if (y != null) this.curOp.scrollTop = y;
}),
getScrollInfo: function() {
var scroller = this.display.scroller, co = scrollerCutOff;
@@ -3241,57 +4118,60 @@
clientHeight: scroller.clientHeight - co, clientWidth: scroller.clientWidth - co};
},
- scrollIntoView: operation(null, function(range, margin) {
- if (range == null) range = {from: this.doc.sel.head, to: null};
- else if (typeof range == "number") range = {from: Pos(range, 0), to: null};
- else if (range.from == null) range = {from: range, to: null};
- if (!range.to) range.to = range.from;
- if (!margin) margin = 0;
-
- var coords = range;
- if (range.from.line != null) {
- this.curOp.scrollToPos = {from: range.from, to: range.to, margin: margin};
- coords = {from: cursorCoords(this, range.from),
- to: cursorCoords(this, range.to)};
+ scrollIntoView: methodOp(function(range, margin) {
+ if (range == null) {
+ range = {from: this.doc.sel.primary().head, to: null};
+ if (margin == null) margin = this.options.cursorScrollMargin;
+ } else if (typeof range == "number") {
+ range = {from: Pos(range, 0), to: null};
+ } else if (range.from == null) {
+ range = {from: range, to: null};
}
- var sPos = calculateScrollPos(this, Math.min(coords.from.left, coords.to.left),
- Math.min(coords.from.top, coords.to.top) - margin,
- Math.max(coords.from.right, coords.to.right),
- Math.max(coords.from.bottom, coords.to.bottom) + margin);
- updateScrollPos(this, sPos.scrollLeft, sPos.scrollTop);
+ if (!range.to) range.to = range.from;
+ range.margin = margin || 0;
+
+ if (range.from.line != null) {
+ resolveScrollToPos(this);
+ this.curOp.scrollToPos = range;
+ } else {
+ var sPos = calculateScrollPos(this, Math.min(range.from.left, range.to.left),
+ Math.min(range.from.top, range.to.top) - range.margin,
+ Math.max(range.from.right, range.to.right),
+ Math.max(range.from.bottom, range.to.bottom) + range.margin);
+ this.scrollTo(sPos.scrollLeft, sPos.scrollTop);
+ }
}),
- setSize: operation(null, function(width, height) {
+ setSize: methodOp(function(width, height) {
function interpret(val) {
return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val;
}
if (width != null) this.display.wrapper.style.width = interpret(width);
if (height != null) this.display.wrapper.style.height = interpret(height);
- if (this.options.lineWrapping)
- this.display.measureLineCache.length = this.display.measureLineCachePos = 0;
+ if (this.options.lineWrapping) clearLineMeasurementCache(this);
this.curOp.forceUpdate = true;
signal(this, "refresh", this);
}),
operation: function(f){return runInOp(this, f);},
- refresh: operation(null, function() {
+ refresh: methodOp(function() {
var oldHeight = this.display.cachedTextHeight;
- clearCaches(this);
- updateScrollPos(this, this.doc.scrollLeft, this.doc.scrollTop);
regChange(this);
+ clearCaches(this);
+ this.scrollTo(this.doc.scrollLeft, this.doc.scrollTop);
if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) > .5)
estimateLineHeights(this);
signal(this, "refresh", this);
}),
- swapDoc: operation(null, function(doc) {
+ swapDoc: methodOp(function(doc) {
var old = this.doc;
old.cm = null;
attachDoc(this, doc);
clearCaches(this);
- resetInput(this, true);
- updateScrollPos(this, doc.scrollLeft, doc.scrollTop);
+ resetInput(this);
+ this.scrollTo(doc.scrollLeft, doc.scrollTop);
signalLater(this, "swapDoc", this, old);
return old;
}),
@@ -3305,10 +4185,10 @@
// OPTION DEFAULTS
- var optionHandlers = CodeMirror.optionHandlers = {};
-
// The default configuration options.
var defaults = CodeMirror.defaults = {};
+ // Functions to run when options are changed.
+ var optionHandlers = CodeMirror.optionHandlers = {};
function option(name, deflt, handle, notOnInit) {
CodeMirror.defaults[name] = deflt;
@@ -3316,6 +4196,7 @@
notOnInit ? function(cm, val, old) {if (old != Init) handle(cm, val, old);} : handle;
}
+ // Passed to option handlers when there is no old value.
var Init = CodeMirror.Init = {toString: function(){return "CodeMirror.Init";}};
// These two are, on init, called from the constructor because they
@@ -3352,9 +4233,6 @@
option("keyMap", "default", keyMapChanged);
option("extraKeys", null);
- option("onKeyEvent", null);
- option("onDragEvent", null);
-
option("lineWrapping", false, wrappingChanged, true);
option("gutters", [], function(cm) {
setGuttersForLineNumbers(cm.options);
@@ -3382,10 +4260,10 @@
cm.display.disabled = true;
} else {
cm.display.disabled = false;
- if (!val) resetInput(cm, true);
+ if (!val) resetInput(cm);
}
});
- option("disableInput", false, function(cm, val) {if (!val) resetInput(cm, true);}, true);
+ option("disableInput", false, function(cm, val) {if (!val) resetInput(cm);}, true);
option("dragDrop", true);
option("cursorBlinkRate", 530);
@@ -3396,11 +4274,10 @@
option("flattenSpans", true, resetModeState, true);
option("addModeClass", false, resetModeState, true);
option("pollInterval", 100);
- option("undoDepth", 40, function(cm, val){cm.doc.history.undoDepth = val;});
- option("historyEventDelay", 500);
+ option("undoDepth", 200, function(cm, val){cm.doc.history.undoDepth = val;});
+ option("historyEventDelay", 1250);
option("viewportMargin", 10, function(cm){cm.refresh();}, true);
option("maxHighlightLength", 10000, resetModeState, true);
- option("crudeMeasuringFrom", 10000);
option("moveInputWithCursor", true, function(cm, val) {
if (!val) cm.display.inputDiv.style.top = cm.display.inputDiv.style.left = 0;
});
@@ -3415,6 +4292,9 @@
// Known modes, by name and by MIME
var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {};
+ // Extra arguments are stored as the mode's dependencies, which is
+ // used by (legacy) mechanisms like loadmode.js to automatically
+ // load a mode. (Preferred mechanism is the require/define calls.)
CodeMirror.defineMode = function(name, mode) {
if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name;
if (arguments.length > 2) {
@@ -3428,11 +4308,14 @@
mimeModes[mime] = spec;
};
+ // Given a MIME type, a {name, ...options} config object, or a name
+ // string, return a mode config object.
CodeMirror.resolveMode = function(spec) {
if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) {
spec = mimeModes[spec];
} else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) {
var found = mimeModes[spec.name];
+ if (typeof found == "string") found = {name: found};
spec = createObj(found, spec);
spec.name = found.name;
} else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) {
@@ -3442,6 +4325,8 @@
else return spec || {name: "null"};
};
+ // Given a mode spec (anything that resolveMode accepts), find and
+ // initialize an actual mode object.
CodeMirror.getMode = function(options, spec) {
var spec = CodeMirror.resolveMode(spec);
var mfactory = modes[spec.name];
@@ -3463,11 +4348,14 @@
return modeObj;
};
+ // Minimal default mode.
CodeMirror.defineMode("null", function() {
return {token: function(stream) {stream.skipToEnd();}};
});
CodeMirror.defineMIME("text/plain", "null");
+ // This can be used to attach properties to mode objects from
+ // outside the actual mode definition.
var modeExtensions = CodeMirror.modeExtensions = {};
CodeMirror.extendMode = function(mode, properties) {
var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {});
@@ -3497,15 +4385,12 @@
helpers[type]._global.push({pred: predicate, val: value});
};
- // UTILITIES
-
- CodeMirror.isWordChar = isWordChar;
-
// MODE STATE HANDLING
- // Utility functions for working with state. Exported because modes
- // sometimes need to do this.
- function copyState(mode, state) {
+ // Utility functions for working with state. Exported because nested
+ // modes need to do this for their inner modes.
+
+ var copyState = CodeMirror.copyState = function(mode, state) {
if (state === true) return state;
if (mode.copyState) return mode.copyState(state);
var nstate = {};
@@ -3515,14 +4400,14 @@
nstate[n] = val;
}
return nstate;
- }
- CodeMirror.copyState = copyState;
+ };
- function startState(mode, a1, a2) {
+ var startState = CodeMirror.startState = function(mode, a1, a2) {
return mode.startState ? mode.startState(a1, a2) : true;
- }
- CodeMirror.startState = startState;
+ };
+ // Given a mode and a state (for that mode), find the inner mode and
+ // state at the position that the state refers to.
CodeMirror.innerMode = function(mode, state) {
while (mode.innerMode) {
var info = mode.innerMode(state);
@@ -3535,49 +4420,73 @@
// STANDARD COMMANDS
+ // Commands are parameter-less actions that can be performed on an
+ // editor, mostly used for keybindings.
var commands = CodeMirror.commands = {
- selectAll: function(cm) {cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()));},
+ selectAll: function(cm) {cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()), sel_dontScroll);},
+ singleSelection: function(cm) {
+ cm.setSelection(cm.getCursor("anchor"), cm.getCursor("head"), sel_dontScroll);
+ },
killLine: function(cm) {
- var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
- if (!sel && cm.getLine(from.line).length == from.ch)
- cm.replaceRange("", from, Pos(from.line + 1, 0), "+delete");
- else cm.replaceRange("", from, sel ? to : Pos(from.line), "+delete");
+ deleteNearSelection(cm, function(range) {
+ if (range.empty()) {
+ var len = getLine(cm.doc, range.head.line).text.length;
+ if (range.head.ch == len && range.head.line < cm.lastLine())
+ return {from: range.head, to: Pos(range.head.line + 1, 0)};
+ else
+ return {from: range.head, to: Pos(range.head.line, len)};
+ } else {
+ return {from: range.from(), to: range.to()};
+ }
+ });
},
deleteLine: function(cm) {
- var l = cm.getCursor().line;
- cm.replaceRange("", Pos(l, 0), Pos(l), "+delete");
+ deleteNearSelection(cm, function(range) {
+ return {from: Pos(range.from().line, 0),
+ to: clipPos(cm.doc, Pos(range.to().line + 1, 0))};
+ });
},
delLineLeft: function(cm) {
- var cur = cm.getCursor();
- cm.replaceRange("", Pos(cur.line, 0), cur, "+delete");
+ deleteNearSelection(cm, function(range) {
+ return {from: Pos(range.from().line, 0), to: range.from()};
+ });
},
undo: function(cm) {cm.undo();},
redo: function(cm) {cm.redo();},
+ undoSelection: function(cm) {cm.undoSelection();},
+ redoSelection: function(cm) {cm.redoSelection();},
goDocStart: function(cm) {cm.extendSelection(Pos(cm.firstLine(), 0));},
goDocEnd: function(cm) {cm.extendSelection(Pos(cm.lastLine()));},
goLineStart: function(cm) {
- cm.extendSelection(lineStart(cm, cm.getCursor().line));
+ cm.extendSelectionsBy(function(range) { return lineStart(cm, range.head.line); }, sel_move);
},
goLineStartSmart: function(cm) {
- var cur = cm.getCursor(), start = lineStart(cm, cur.line);
- var line = cm.getLineHandle(start.line);
- var order = getOrder(line);
- if (!order || order[0].level == 0) {
- var firstNonWS = Math.max(0, line.text.search(/\S/));
- var inWS = cur.line == start.line && cur.ch <= firstNonWS && cur.ch;
- cm.extendSelection(Pos(start.line, inWS ? 0 : firstNonWS));
- } else cm.extendSelection(start);
+ cm.extendSelectionsBy(function(range) {
+ var start = lineStart(cm, range.head.line);
+ var line = cm.getLineHandle(start.line);
+ var order = getOrder(line);
+ if (!order || order[0].level == 0) {
+ var firstNonWS = Math.max(0, line.text.search(/\S/));
+ var inWS = range.head.line == start.line && range.head.ch <= firstNonWS && range.head.ch;
+ return Pos(start.line, inWS ? 0 : firstNonWS);
+ }
+ return start;
+ }, sel_move);
},
goLineEnd: function(cm) {
- cm.extendSelection(lineEnd(cm, cm.getCursor().line));
+ cm.extendSelectionsBy(function(range) { return lineEnd(cm, range.head.line); }, sel_move);
},
goLineRight: function(cm) {
- var top = cm.charCoords(cm.getCursor(), "div").top + 5;
- cm.extendSelection(cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div"));
+ cm.extendSelectionsBy(function(range) {
+ var top = cm.charCoords(range.head, "div").top + 5;
+ return cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div");
+ }, sel_move);
},
goLineLeft: function(cm) {
- var top = cm.charCoords(cm.getCursor(), "div").top + 5;
- cm.extendSelection(cm.coordsChar({left: 0, top: top}, "div"));
+ cm.extendSelectionsBy(function(range) {
+ var top = cm.charCoords(range.head, "div").top + 5;
+ return cm.coordsChar({left: 0, top: top}, "div");
+ }, sel_move);
},
goLineUp: function(cm) {cm.moveV(-1, "line");},
goLineDown: function(cm) {cm.moveV(1, "line");},
@@ -3600,24 +4509,32 @@
indentAuto: function(cm) {cm.indentSelection("smart");},
indentMore: function(cm) {cm.indentSelection("add");},
indentLess: function(cm) {cm.indentSelection("subtract");},
- insertTab: function(cm) {
- cm.replaceSelection("\t", "end", "+input");
- },
+ insertTab: function(cm) {cm.replaceSelection("\t");},
defaultTab: function(cm) {
if (cm.somethingSelected()) cm.indentSelection("add");
- else cm.replaceSelection("\t", "end", "+input");
+ else cm.execCommand("insertTab");
},
transposeChars: function(cm) {
- var cur = cm.getCursor(), line = cm.getLine(cur.line);
- if (cur.ch > 0 && cur.ch < line.length - 1)
- cm.replaceRange(line.charAt(cur.ch) + line.charAt(cur.ch - 1),
- Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1));
+ runInOp(cm, function() {
+ var ranges = cm.listSelections();
+ for (var i = 0; i < ranges.length; i++) {
+ var cur = ranges[i].head, line = getLine(cm.doc, cur.line).text;
+ if (cur.ch > 0 && cur.ch < line.length - 1)
+ cm.replaceRange(line.charAt(cur.ch) + line.charAt(cur.ch - 1),
+ Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1));
+ }
+ });
},
newlineAndIndent: function(cm) {
- operation(cm, function() {
- cm.replaceSelection("\n", "end", "+input");
- cm.indentLine(cm.getCursor().line, null, true);
- })();
+ runInOp(cm, function() {
+ var len = cm.listSelections().length;
+ for (var i = 0; i < len; i++) {
+ var range = cm.listSelections()[i];
+ cm.replaceRange("\n", range.anchor, range.head, "+input");
+ cm.indentLine(range.from().line + 1, null, true);
+ ensureCursorVisible(cm);
+ }
+ });
},
toggleOverwrite: function(cm) {cm.toggleOverwrite();}
};
@@ -3630,17 +4547,20 @@
"End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown",
"Delete": "delCharAfter", "Backspace": "delCharBefore", "Shift-Backspace": "delCharBefore",
"Tab": "defaultTab", "Shift-Tab": "indentAuto",
- "Enter": "newlineAndIndent", "Insert": "toggleOverwrite"
+ "Enter": "newlineAndIndent", "Insert": "toggleOverwrite",
+ "Esc": "singleSelection"
};
// Note that the save and find-related commands aren't defined by
- // default. Unknown commands are simply ignored.
+ // default. User code or addons can define them. Unknown commands
+ // are simply ignored.
keyMap.pcDefault = {
"Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo",
- "Ctrl-Home": "goDocStart", "Alt-Up": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Down": "goDocEnd",
+ "Ctrl-Home": "goDocStart", "Ctrl-Up": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Down": "goDocEnd",
"Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd",
"Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S": "save", "Ctrl-F": "find",
"Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll",
"Ctrl-[": "indentLess", "Ctrl-]": "indentMore",
+ "Ctrl-U": "undoSelection", "Shift-Ctrl-U": "redoSelection", "Alt-U": "redoSelection",
fallthrough: "basic"
};
keyMap.macDefault = {
@@ -3650,15 +4570,17 @@
"Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find",
"Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll",
"Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delLineLeft",
+ "Cmd-U": "undoSelection", "Shift-Cmd-U": "redoSelection",
fallthrough: ["basic", "emacsy"]
};
- keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault;
+ // Very basic readline/emacs-style bindings, which are standard on Mac.
keyMap.emacsy = {
"Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown",
"Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd",
"Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore",
"Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars"
};
+ keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault;
// KEYMAP DISPATCH
@@ -3667,7 +4589,11 @@
else return val;
}
- function lookupKey(name, maps, handle) {
+ // Given an array of keymaps and a key name, call handle on any
+ // bindings found, until that returns a truthy value, at which point
+ // we consider the key handled. Implements things like binding a key
+ // to false stopping further handling and keymap fallthrough.
+ var lookupKey = CodeMirror.lookupKey = function(name, maps, handle) {
function lookup(map) {
map = getKeyMap(map);
var found = map[name];
@@ -3679,7 +4605,7 @@
if (fallthrough == null) return false;
if (Object.prototype.toString.call(fallthrough) != "[object Array]")
return lookup(fallthrough);
- for (var i = 0, e = fallthrough.length; i < e; ++i) {
+ for (var i = 0; i < fallthrough.length; ++i) {
var done = lookup(fallthrough[i]);
if (done) return done;
}
@@ -3690,13 +4616,18 @@
var done = lookup(maps[i]);
if (done) return done != "stop";
}
- }
- function isModifierKey(event) {
+ };
+
+ // Modifier key presses don't count as 'real' key presses for the
+ // purpose of keymap fallthrough.
+ var isModifierKey = CodeMirror.isModifierKey = function(event) {
var name = keyNames[event.keyCode];
return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod";
- }
- function keyName(event, noShift) {
- if (opera && event.keyCode == 34 && event["char"]) return false;
+ };
+
+ // Look up the name of a key as indicated by an event object.
+ var keyName = CodeMirror.keyName = function(event, noShift) {
+ if (presto && event.keyCode == 34 && event["char"]) return false;
var name = keyNames[event.keyCode];
if (name == null || event.altGraphKey) return false;
if (event.altKey) name = "Alt-" + name;
@@ -3704,10 +4635,7 @@
if (flipCtrlCmd ? event.ctrlKey : event.metaKey) name = "Cmd-" + name;
if (!noShift && event.shiftKey) name = "Shift-" + name;
return name;
- }
- CodeMirror.lookupKey = lookupKey;
- CodeMirror.isModifierKey = isModifierKey;
- CodeMirror.keyName = keyName;
+ };
// FROMTEXTAREA
@@ -3721,9 +4649,7 @@
// Set autofocus to true if this textarea is focused, or if it has
// autofocus and no other element is focused.
if (options.autofocus == null) {
- var hasFocus = document.body;
- // doc.activeElement occasionally throws on IE
- try { hasFocus = document.activeElement; } catch(e) {}
+ var hasFocus = activeElt();
options.autofocus = hasFocus == textarea ||
textarea.getAttribute("autofocus") != null && hasFocus == document.body;
}
@@ -3769,14 +4695,13 @@
// Fed to the mode parsers, provides helper functions to make
// parsers more succinct.
- // The character stream used by a mode's parser.
- function StringStream(string, tabSize) {
+ var StringStream = CodeMirror.StringStream = function(string, tabSize) {
this.pos = this.start = 0;
this.string = string;
this.tabSize = tabSize || 8;
this.lastColumnPos = this.lastColumnValue = 0;
this.lineStart = 0;
- }
+ };
StringStream.prototype = {
eol: function() {return this.pos >= this.string.length;},
@@ -3841,18 +4766,27 @@
finally { this.lineStart -= n; }
}
};
- CodeMirror.StringStream = StringStream;
// TEXTMARKERS
- function TextMarker(doc, type) {
+ // Created with markText and setBookmark methods. A TextMarker is a
+ // handle that can be used to clear or find a marked position in the
+ // document. Line objects hold arrays (markedSpans) containing
+ // {from, to, marker} object pointing to such marker objects, and
+ // indicating that such a marker is present on that line. Multiple
+ // lines may point to the same marker when it spans across lines.
+ // The spans will have null for their from/to properties when the
+ // marker continues beyond the start/end of the line. Markers have
+ // links back to the lines they currently touch.
+
+ var TextMarker = CodeMirror.TextMarker = function(doc, type) {
this.lines = [];
this.type = type;
this.doc = doc;
- }
- CodeMirror.TextMarker = TextMarker;
+ };
eventMixin(TextMarker);
+ // Clear the marker.
TextMarker.prototype.clear = function() {
if (this.explicitlyCleared) return;
var cm = this.doc.cm, withOp = cm && !cm.curOp;
@@ -3865,15 +4799,17 @@
for (var i = 0; i < this.lines.length; ++i) {
var line = this.lines[i];
var span = getMarkedSpanFor(line.markedSpans, this);
- if (span.to != null) max = lineNo(line);
+ if (cm && !this.collapsed) regLineChange(cm, lineNo(line), "text");
+ else if (cm) {
+ if (span.to != null) max = lineNo(line);
+ if (span.from != null) min = lineNo(line);
+ }
line.markedSpans = removeMarkedSpan(line.markedSpans, span);
- if (span.from != null)
- min = lineNo(line);
- else if (this.collapsed && !lineIsHidden(this.doc, line) && cm)
+ if (span.from == null && this.collapsed && !lineIsHidden(this.doc, line) && cm)
updateLineHeight(line, textHeight(cm.display));
}
if (cm && this.collapsed && !cm.options.lineWrapping) for (var i = 0; i < this.lines.length; ++i) {
- var visual = visualLine(cm.doc, this.lines[i]), len = lineLength(cm.doc, visual);
+ var visual = visualLine(this.lines[i]), len = lineLength(visual);
if (len > cm.display.maxLineLength) {
cm.display.maxLine = visual;
cm.display.maxLineLength = len;
@@ -3881,46 +4817,61 @@
}
}
- if (min != null && cm) regChange(cm, min, max + 1);
+ if (min != null && cm && this.collapsed) regChange(cm, min, max + 1);
this.lines.length = 0;
this.explicitlyCleared = true;
if (this.atomic && this.doc.cantEdit) {
this.doc.cantEdit = false;
- if (cm) reCheckSelection(cm);
+ if (cm) reCheckSelection(cm.doc);
}
+ if (cm) signalLater(cm, "markerCleared", cm, this);
if (withOp) endOperation(cm);
};
- TextMarker.prototype.find = function(bothSides) {
+ // Find the position of the marker in the document. Returns a {from,
+ // to} object by default. Side can be passed to get a specific side
+ // -- 0 (both), -1 (left), or 1 (right). When lineObj is true, the
+ // Pos objects returned contain a line object, rather than a line
+ // number (used to prevent looking up the same line twice).
+ TextMarker.prototype.find = function(side, lineObj) {
+ if (side == null && this.type == "bookmark") side = 1;
var from, to;
for (var i = 0; i < this.lines.length; ++i) {
var line = this.lines[i];
var span = getMarkedSpanFor(line.markedSpans, this);
- if (span.from != null || span.to != null) {
- var found = lineNo(line);
- if (span.from != null) from = Pos(found, span.from);
- if (span.to != null) to = Pos(found, span.to);
+ if (span.from != null) {
+ from = Pos(lineObj ? line : lineNo(line), span.from);
+ if (side == -1) return from;
+ }
+ if (span.to != null) {
+ to = Pos(lineObj ? line : lineNo(line), span.to);
+ if (side == 1) return to;
}
}
- if (this.type == "bookmark" && !bothSides) return from;
return from && {from: from, to: to};
};
+ // Signals that the marker's widget changed, and surrounding layout
+ // should be recomputed.
TextMarker.prototype.changed = function() {
- var pos = this.find(), cm = this.doc.cm;
+ var pos = this.find(-1, true), widget = this, cm = this.doc.cm;
if (!pos || !cm) return;
- if (this.type != "bookmark") pos = pos.from;
- var line = getLine(this.doc, pos.line);
- clearCachedMeasurement(cm, line);
- if (pos.line >= cm.display.showingFrom && pos.line < cm.display.showingTo) {
- for (var node = cm.display.lineDiv.firstChild; node; node = node.nextSibling) if (node.lineObj == line) {
- if (node.offsetHeight != line.height) updateLineHeight(line, node.offsetHeight);
- break;
+ runInOp(cm, function() {
+ var line = pos.line, lineN = lineNo(pos.line);
+ var view = findViewForLine(cm, lineN);
+ if (view) {
+ clearLineMeasurementCacheFor(view);
+ cm.curOp.selectionChanged = cm.curOp.forceUpdate = true;
}
- runInOp(cm, function() {
- cm.curOp.selectionChanged = cm.curOp.forceUpdate = cm.curOp.updateMaxLine = true;
- });
- }
+ cm.curOp.updateMaxLine = true;
+ if (!lineIsHidden(widget.doc, line) && widget.height != null) {
+ var oldHeight = widget.height;
+ widget.height = null;
+ var dHeight = widgetHeight(widget) - oldHeight;
+ if (dHeight)
+ updateLineHeight(line, line.height + dHeight);
+ }
+ });
};
TextMarker.prototype.attachLine = function(line) {
@@ -3939,20 +4890,31 @@
}
};
+ // Collapsed markers have unique ids, in order to be able to order
+ // them, which is needed for uniquely determining an outer marker
+ // when they overlap (they may nest, but not partially overlap).
var nextMarkerId = 0;
+ // Create a marker, wire it up to the right lines, and
function markText(doc, from, to, options, type) {
+ // Shared markers (across linked documents) are handled separately
+ // (markTextShared will call out to this again, once per
+ // document).
if (options && options.shared) return markTextShared(doc, from, to, options, type);
+ // Ensure we are in an operation.
if (doc.cm && !doc.cm.curOp) return operation(doc.cm, markText)(doc, from, to, options, type);
- var marker = new TextMarker(doc, type);
+ var marker = new TextMarker(doc, type), diff = cmp(from, to);
if (options) copyObj(options, marker);
- if (posLess(to, from) || posEq(from, to) && marker.clearWhenEmpty !== false)
+ // Don't connect empty markers unless clearWhenEmpty is false
+ if (diff > 0 || diff == 0 && marker.clearWhenEmpty !== false)
return marker;
if (marker.replacedWith) {
+ // Showing up as a widget implies collapsed (widget replaces text)
marker.collapsed = true;
- marker.replacedWith = elt("span", [marker.replacedWith], "CodeMirror-widget");
- if (!options.handleMouseEvents) marker.replacedWith.ignoreEvents = true;
+ marker.widgetNode = elt("span", [marker.replacedWith], "CodeMirror-widget");
+ if (!options.handleMouseEvents) marker.widgetNode.ignoreEvents = true;
+ if (options.insertLeft) marker.widgetNode.insertLeft = true;
}
if (marker.collapsed) {
if (conflictingCollapsedRange(doc, from.line, from, to, marker) ||
@@ -3962,20 +4924,19 @@
}
if (marker.addToHistory)
- addToHistory(doc, {from: from, to: to, origin: "markText"},
- {head: doc.sel.head, anchor: doc.sel.anchor}, NaN);
+ addChangeToHistory(doc, {from: from, to: to, origin: "markText"}, doc.sel, NaN);
var curLine = from.line, cm = doc.cm, updateMaxLine;
doc.iter(curLine, to.line + 1, function(line) {
- if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(doc, line) == cm.display.maxLine)
+ if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(line) == cm.display.maxLine)
updateMaxLine = true;
- var span = {from: null, to: null, marker: marker};
- if (curLine == from.line) span.from = from.ch;
- if (curLine == to.line) span.to = to.ch;
if (marker.collapsed && curLine != from.line) updateLineHeight(line, 0);
- addMarkedSpan(line, span);
+ addMarkedSpan(line, new MarkedSpan(marker,
+ curLine == from.line ? from.ch : null,
+ curLine == to.line ? to.ch : null));
++curLine;
});
+ // lineIsHidden depends on the presence of the spans, so needs a second pass
if (marker.collapsed) doc.iter(from.line, to.line + 1, function(line) {
if (lineIsHidden(doc, line)) updateLineHeight(line, 0);
});
@@ -3992,25 +4953,31 @@
marker.atomic = true;
}
if (cm) {
+ // Sync editor state
if (updateMaxLine) cm.curOp.updateMaxLine = true;
- if (marker.className || marker.title || marker.startStyle || marker.endStyle || marker.collapsed)
+ if (marker.collapsed)
regChange(cm, from.line, to.line + 1);
- if (marker.atomic) reCheckSelection(cm);
+ else if (marker.className || marker.title || marker.startStyle || marker.endStyle)
+ for (var i = from.line; i <= to.line; i++) regLineChange(cm, i, "text");
+ if (marker.atomic) reCheckSelection(cm.doc);
+ signalLater(cm, "markerAdded", cm, marker);
}
return marker;
}
// SHARED TEXTMARKERS
- function SharedTextMarker(markers, primary) {
+ // A shared marker spans multiple linked documents. It is
+ // implemented as a meta-marker-object controlling multiple normal
+ // markers.
+ var SharedTextMarker = CodeMirror.SharedTextMarker = function(markers, primary) {
this.markers = markers;
this.primary = primary;
for (var i = 0, me = this; i < markers.length; ++i) {
markers[i].parent = this;
on(markers[i], "clear", function(){me.clear();});
}
- }
- CodeMirror.SharedTextMarker = SharedTextMarker;
+ };
eventMixin(SharedTextMarker);
SharedTextMarker.prototype.clear = function() {
@@ -4020,17 +4987,17 @@
this.markers[i].clear();
signalLater(this, "clear");
};
- SharedTextMarker.prototype.find = function() {
- return this.primary.find();
+ SharedTextMarker.prototype.find = function(side, lineObj) {
+ return this.primary.find(side, lineObj);
};
function markTextShared(doc, from, to, options, type) {
options = copyObj(options);
options.shared = false;
var markers = [markText(doc, from, to, options, type)], primary = markers[0];
- var widget = options.replacedWith;
+ var widget = options.widgetNode;
linkedDocs(doc, function(doc) {
- if (widget) options.replacedWith = widget.cloneNode(true);
+ if (widget) options.widgetNode = widget.cloneNode(true);
markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, type));
for (var i = 0; i < doc.linked.length; ++i)
if (doc.linked[i].isParent) return;
@@ -4041,56 +5008,71 @@
// TEXTMARKER SPANS
+ function MarkedSpan(marker, from, to) {
+ this.marker = marker;
+ this.from = from; this.to = to;
+ }
+
+ // Search an array of spans for a span matching the given marker.
function getMarkedSpanFor(spans, marker) {
if (spans) for (var i = 0; i < spans.length; ++i) {
var span = spans[i];
if (span.marker == marker) return span;
}
}
+ // Remove a span from an array, returning undefined if no spans are
+ // left (we don't store arrays for lines without spans).
function removeMarkedSpan(spans, span) {
for (var r, i = 0; i < spans.length; ++i)
if (spans[i] != span) (r || (r = [])).push(spans[i]);
return r;
}
+ // Add a span to a line.
function addMarkedSpan(line, span) {
line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span];
span.marker.attachLine(line);
}
+ // Used for the algorithm that adjusts markers for a change in the
+ // document. These functions cut an array of spans at a given
+ // character position, returning an array of remaining chunks (or
+ // undefined if nothing remains).
function markedSpansBefore(old, startCh, isInsert) {
if (old) for (var i = 0, nw; i < old.length; ++i) {
var span = old[i], marker = span.marker;
var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh);
if (startsBefore || span.from == startCh && marker.type == "bookmark" && (!isInsert || !span.marker.insertLeft)) {
var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh);
- (nw || (nw = [])).push({from: span.from,
- to: endsAfter ? null : span.to,
- marker: marker});
+ (nw || (nw = [])).push(new MarkedSpan(marker, span.from, endsAfter ? null : span.to));
}
}
return nw;
}
-
function markedSpansAfter(old, endCh, isInsert) {
if (old) for (var i = 0, nw; i < old.length; ++i) {
var span = old[i], marker = span.marker;
var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh);
if (endsAfter || span.from == endCh && marker.type == "bookmark" && (!isInsert || span.marker.insertLeft)) {
var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh);
- (nw || (nw = [])).push({from: startsBefore ? null : span.from - endCh,
- to: span.to == null ? null : span.to - endCh,
- marker: marker});
+ (nw || (nw = [])).push(new MarkedSpan(marker, startsBefore ? null : span.from - endCh,
+ span.to == null ? null : span.to - endCh));
}
}
return nw;
}
+ // Given a change object, compute the new set of marker spans that
+ // cover the line in which the change took place. Removes spans
+ // entirely within the change, reconnects spans belonging to the
+ // same marker that appear on both sides of the change, and cuts off
+ // spans partially within the change. Returns an array of span
+ // arrays with one element for each line in (after) the change.
function stretchSpansOverChange(doc, change) {
var oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line).markedSpans;
var oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).markedSpans;
if (!oldFirst && !oldLast) return null;
- var startCh = change.from.ch, endCh = change.to.ch, isInsert = posEq(change.from, change.to);
+ var startCh = change.from.ch, endCh = change.to.ch, isInsert = cmp(change.from, change.to) == 0;
// Get the spans that 'stick out' on both sides
var first = markedSpansBefore(oldFirst, startCh, isInsert);
var last = markedSpansAfter(oldLast, endCh, isInsert);
@@ -4136,7 +5118,7 @@
if (gap > 0 && first)
for (var i = 0; i < first.length; ++i)
if (first[i].to == null)
- (gapMarkers || (gapMarkers = [])).push({from: null, to: null, marker: first[i].marker});
+ (gapMarkers || (gapMarkers = [])).push(new MarkedSpan(first[i].marker, null, null));
for (var i = 0; i < gap; ++i)
newMarkers.push(gapMarkers);
newMarkers.push(last);
@@ -4144,6 +5126,8 @@
return newMarkers;
}
+ // Remove spans that are empty and don't have a clearWhenEmpty
+ // option of false.
function clearEmptySpans(spans) {
for (var i = 0; i < spans.length; ++i) {
var span = spans[i];
@@ -4154,6 +5138,10 @@
return spans;
}
+ // Used for un/re-doing changes from the history. Combines the
+ // result of computing the existing spans with the set of spans that
+ // existed in the history (so that deleting around a span and then
+ // undoing brings back the span).
function mergeOldSpans(doc, change) {
var old = getOldSpans(doc, change);
var stretched = stretchSpansOverChange(doc, change);
@@ -4176,6 +5164,7 @@
return old;
}
+ // Used to 'clip' out readOnly ranges when making a change.
function removeReadOnlyRanges(doc, from, to) {
var markers = null;
doc.iter(from.line, to.line + 1, function(line) {
@@ -4188,14 +5177,14 @@
if (!markers) return null;
var parts = [{from: from, to: to}];
for (var i = 0; i < markers.length; ++i) {
- var mk = markers[i], m = mk.find();
+ var mk = markers[i], m = mk.find(0);
for (var j = 0; j < parts.length; ++j) {
var p = parts[j];
- if (posLess(p.to, m.from) || posLess(m.to, p.from)) continue;
- var newParts = [j, 1];
- if (posLess(p.from, m.from) || !mk.inclusiveLeft && posEq(p.from, m.from))
+ if (cmp(p.to, m.from) < 0 || cmp(p.from, m.to) > 0) continue;
+ var newParts = [j, 1], dfrom = cmp(p.from, m.from), dto = cmp(p.to, m.to);
+ if (dfrom < 0 || !mk.inclusiveLeft && !dfrom)
newParts.push({from: p.from, to: m.from});
- if (posLess(m.to, p.to) || !mk.inclusiveRight && posEq(p.to, m.to))
+ if (dto > 0 || !mk.inclusiveRight && !dto)
newParts.push({from: m.to, to: p.to});
parts.splice.apply(parts, newParts);
j += newParts.length - 1;
@@ -4204,9 +5193,29 @@
return parts;
}
+ // Connect or disconnect spans from a line.
+ function detachMarkedSpans(line) {
+ var spans = line.markedSpans;
+ if (!spans) return;
+ for (var i = 0; i < spans.length; ++i)
+ spans[i].marker.detachLine(line);
+ line.markedSpans = null;
+ }
+ function attachMarkedSpans(line, spans) {
+ if (!spans) return;
+ for (var i = 0; i < spans.length; ++i)
+ spans[i].marker.attachLine(line);
+ line.markedSpans = spans;
+ }
+
+ // Helpers used when computing which overlapping collapsed span
+ // counts as the larger one.
function extraLeft(marker) { return marker.inclusiveLeft ? -1 : 0; }
function extraRight(marker) { return marker.inclusiveRight ? 1 : 0; }
+ // Returns a number indicating which of two overlapping collapsed
+ // spans is larger (and thus includes the other). Falls back to
+ // comparing ids when the spans cover exactly the same range.
function compareCollapsedMarkers(a, b) {
var lenDiff = a.lines.length - b.lines.length;
if (lenDiff != 0) return lenDiff;
@@ -4218,6 +5227,8 @@
return b.id - a.id;
}
+ // Find out whether a line ends or starts in a collapsed span. If
+ // so, return the marker for that span.
function collapsedSpanAtSide(line, start) {
var sps = sawCollapsedSpans && line.markedSpans, found;
if (sps) for (var sp, i = 0; i < sps.length; ++i) {
@@ -4231,13 +5242,16 @@
function collapsedSpanAtStart(line) { return collapsedSpanAtSide(line, true); }
function collapsedSpanAtEnd(line) { return collapsedSpanAtSide(line, false); }
+ // Test whether there exists a collapsed span that partially
+ // overlaps (covers the start or end, but not both) of a new span.
+ // Such overlap is not allowed.
function conflictingCollapsedRange(doc, lineNo, from, to, marker) {
var line = getLine(doc, lineNo);
var sps = sawCollapsedSpans && line.markedSpans;
if (sps) for (var i = 0; i < sps.length; ++i) {
var sp = sps[i];
if (!sp.marker.collapsed) continue;
- var found = sp.marker.find(true);
+ var found = sp.marker.find(0);
var fromCmp = cmp(found.from, from) || extraLeft(sp.marker) - extraLeft(marker);
var toCmp = cmp(found.to, to) || extraRight(sp.marker) - extraRight(marker);
if (fromCmp >= 0 && toCmp <= 0 || fromCmp <= 0 && toCmp >= 0) continue;
@@ -4247,57 +5261,80 @@
}
}
- function visualLine(doc, line) {
+ // A visual line is a line as drawn on the screen. Folding, for
+ // example, can cause multiple logical lines to appear on the same
+ // visual line. This finds the start of the visual line that the
+ // given line is part of (usually that is the line itself).
+ function visualLine(line) {
var merged;
while (merged = collapsedSpanAtStart(line))
- line = getLine(doc, merged.find().from.line);
+ line = merged.find(-1, true).line;
return line;
}
+ // Returns an array of logical lines that continue the visual line
+ // started by the argument, or undefined if there are no such lines.
+ function visualLineContinued(line) {
+ var merged, lines;
+ while (merged = collapsedSpanAtEnd(line)) {
+ line = merged.find(1, true).line;
+ (lines || (lines = [])).push(line);
+ }
+ return lines;
+ }
+
+ // Get the line number of the start of the visual line that the
+ // given line number is part of.
+ function visualLineNo(doc, lineN) {
+ var line = getLine(doc, lineN), vis = visualLine(line);
+ if (line == vis) return lineN;
+ return lineNo(vis);
+ }
+ // Get the line number of the start of the next visual line after
+ // the given line.
+ function visualLineEndNo(doc, lineN) {
+ if (lineN > doc.lastLine()) return lineN;
+ var line = getLine(doc, lineN), merged;
+ if (!lineIsHidden(doc, line)) return lineN;
+ while (merged = collapsedSpanAtEnd(line))
+ line = merged.find(1, true).line;
+ return lineNo(line) + 1;
+ }
+
+ // Compute whether a line is hidden. Lines count as hidden when they
+ // are part of a visual line that starts with another line, or when
+ // they are entirely covered by collapsed, non-widget span.
function lineIsHidden(doc, line) {
var sps = sawCollapsedSpans && line.markedSpans;
if (sps) for (var sp, i = 0; i < sps.length; ++i) {
sp = sps[i];
if (!sp.marker.collapsed) continue;
if (sp.from == null) return true;
- if (sp.marker.replacedWith) continue;
+ if (sp.marker.widgetNode) continue;
if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp))
return true;
}
}
function lineIsHiddenInner(doc, line, span) {
if (span.to == null) {
- var end = span.marker.find().to, endLine = getLine(doc, end.line);
- return lineIsHiddenInner(doc, endLine, getMarkedSpanFor(endLine.markedSpans, span.marker));
+ var end = span.marker.find(1, true);
+ return lineIsHiddenInner(doc, end.line, getMarkedSpanFor(end.line.markedSpans, span.marker));
}
if (span.marker.inclusiveRight && span.to == line.text.length)
return true;
for (var sp, i = 0; i < line.markedSpans.length; ++i) {
sp = line.markedSpans[i];
- if (sp.marker.collapsed && !sp.marker.replacedWith && sp.from == span.to &&
+ if (sp.marker.collapsed && !sp.marker.widgetNode && sp.from == span.to &&
(sp.to == null || sp.to != span.from) &&
(sp.marker.inclusiveLeft || span.marker.inclusiveRight) &&
lineIsHiddenInner(doc, line, sp)) return true;
}
}
- function detachMarkedSpans(line) {
- var spans = line.markedSpans;
- if (!spans) return;
- for (var i = 0; i < spans.length; ++i)
- spans[i].marker.detachLine(line);
- line.markedSpans = null;
- }
-
- function attachMarkedSpans(line, spans) {
- if (!spans) return;
- for (var i = 0; i < spans.length; ++i)
- spans[i].marker.attachLine(line);
- line.markedSpans = spans;
- }
-
// LINE WIDGETS
+ // Line widgets are block elements displayed above or below a line.
+
var LineWidget = CodeMirror.LineWidget = function(cm, node, options) {
if (options) for (var opt in options) if (options.hasOwnProperty(opt))
this[opt] = options[opt];
@@ -4305,38 +5342,39 @@
this.node = node;
};
eventMixin(LineWidget);
- function widgetOperation(f) {
- return function() {
- var withOp = !this.cm.curOp;
- if (withOp) startOperation(this.cm);
- try {var result = f.apply(this, arguments);}
- finally {if (withOp) endOperation(this.cm);}
- return result;
- };
+
+ function adjustScrollWhenAboveVisible(cm, line, diff) {
+ if (heightAtLine(line) < ((cm.curOp && cm.curOp.scrollTop) || cm.doc.scrollTop))
+ addToScrollPos(cm, null, diff);
}
- LineWidget.prototype.clear = widgetOperation(function() {
- var ws = this.line.widgets, no = lineNo(this.line);
+
+ LineWidget.prototype.clear = function() {
+ var cm = this.cm, ws = this.line.widgets, line = this.line, no = lineNo(line);
if (no == null || !ws) return;
for (var i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1);
- if (!ws.length) this.line.widgets = null;
- var aboveVisible = heightAtLine(this.cm, this.line) < this.cm.doc.scrollTop;
- updateLineHeight(this.line, Math.max(0, this.line.height - widgetHeight(this)));
- if (aboveVisible) addToScrollPos(this.cm, 0, -this.height);
- regChange(this.cm, no, no + 1);
- });
- LineWidget.prototype.changed = widgetOperation(function() {
- var oldH = this.height;
+ if (!ws.length) line.widgets = null;
+ var height = widgetHeight(this);
+ runInOp(cm, function() {
+ adjustScrollWhenAboveVisible(cm, line, -height);
+ regLineChange(cm, no, "widget");
+ updateLineHeight(line, Math.max(0, line.height - height));
+ });
+ };
+ LineWidget.prototype.changed = function() {
+ var oldH = this.height, cm = this.cm, line = this.line;
this.height = null;
var diff = widgetHeight(this) - oldH;
if (!diff) return;
- updateLineHeight(this.line, this.line.height + diff);
- var no = lineNo(this.line);
- regChange(this.cm, no, no + 1);
- });
+ runInOp(cm, function() {
+ cm.curOp.forceUpdate = true;
+ adjustScrollWhenAboveVisible(cm, line, diff);
+ updateLineHeight(line, line.height + diff);
+ });
+ };
function widgetHeight(widget) {
if (widget.height != null) return widget.height;
- if (!widget.node.parentNode || widget.node.parentNode.nodeType != 1)
+ if (!contains(document.body, widget.node))
removeChildrenAndAdd(widget.cm.display.measure, elt("div", [widget.node], null, "position: relative"));
return widget.height = widget.node.offsetHeight;
}
@@ -4344,15 +5382,16 @@
function addLineWidget(cm, handle, node, options) {
var widget = new LineWidget(cm, node, options);
if (widget.noHScroll) cm.display.alignWidgets = true;
- changeLine(cm, handle, function(line) {
+ changeLine(cm, handle, "widget", function(line) {
var widgets = line.widgets || (line.widgets = []);
if (widget.insertAt == null) widgets.push(widget);
else widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insertAt)), 0, widget);
widget.line = line;
- if (!lineIsHidden(cm.doc, line) || widget.showIfHidden) {
- var aboveVisible = heightAtLine(cm, line) < cm.doc.scrollTop;
+ if (!lineIsHidden(cm.doc, line)) {
+ var aboveVisible = heightAtLine(line) < cm.doc.scrollTop;
updateLineHeight(line, line.height + widgetHeight(widget));
- if (aboveVisible) addToScrollPos(cm, 0, widget.height);
+ if (aboveVisible) addToScrollPos(cm, null, widget.height);
+ cm.curOp.forceUpdate = true;
}
return true;
});
@@ -4371,6 +5410,9 @@
eventMixin(Line);
Line.prototype.lineNo = function() { return lineNo(this); };
+ // Change the content (text, markers) of a line. Automatically
+ // invalidates cached information and tries to re-estimate the
+ // line's height.
function updateLine(line, text, markedSpans, estimateHeight) {
line.text = text;
if (line.stateAfter) line.stateAfter = null;
@@ -4382,14 +5424,13 @@
if (estHeight != line.height) updateLineHeight(line, estHeight);
}
+ // Detach a line from the document tree and its markers.
function cleanUpLine(line) {
line.parent = null;
detachMarkedSpans(line);
}
- // Run the given mode's parser over a line, update the styles
- // array, which contains alternating fragments of text and CSS
- // classes.
+ // Run the given mode's parser over a line, calling f for each token.
function runMode(cm, text, mode, state, f, forceToEnd) {
var flattenSpans = mode.flattenSpans;
if (flattenSpans == null) flattenSpans = cm.options.flattenSpans;
@@ -4423,6 +5464,10 @@
}
}
+ // Compute a style array (an array starting with a mode generation
+ // -- for invalidation -- followed by pairs of end positions and
+ // style strings), which is used to highlight the tokens on the
+ // line.
function highlightLine(cm, line, state, forceToEnd) {
// A styles array always starts with a number identifying the
// mode/overlays that it is based on (for easy invalidation).
@@ -4468,7 +5513,8 @@
}
// Lightweight form of highlight -- proceed over this line and
- // update state, but don't save a style array.
+ // update state, but don't save a style array. Used for lines that
+ // aren't currently visible.
function processLine(cm, text, state, startAt) {
var mode = cm.doc.mode;
var stream = new StringStream(text, cm.options.tabSize);
@@ -4480,6 +5526,9 @@
}
}
+ // Convert a style as returned by a mode (either null, or a string
+ // containing one or more styles) to a CSS style. This is cached,
+ // and also looks for line-wide styles.
var styleToClassCache = {}, styleToClassCacheWithMode = {};
function interpretTokenStyle(style, builder) {
if (!style) return null;
@@ -4499,54 +5548,48 @@
(cache[style] = style.replace(/\S+/g, "cm-$&"));
}
- function buildLineContent(cm, realLine, measure, copyWidgets) {
- var merged, line = realLine, empty = true;
- while (merged = collapsedSpanAtStart(line))
- line = getLine(cm.doc, merged.find().from.line);
+ // Render the DOM representation of the text of a line. Also builds
+ // up a 'line map', which points at the DOM nodes that represent
+ // specific stretches of text, and is used by the measuring code.
+ // The returned object contains the DOM node, this map, and
+ // information about line-wide styles that were set by the mode.
+ function buildLineContent(cm, lineView) {
+ // The padding-right forces the element to have a 'border', which
+ // is needed on Webkit to be able to get line-level bounding
+ // rectangles for it (in measureChar).
+ var content = elt("span", null, null, webkit ? "padding-right: .1px" : null);
+ var builder = {pre: elt("pre", [content]), content: content, col: 0, pos: 0, cm: cm};
+ lineView.measure = {};
- var builder = {pre: elt("pre"), col: 0, pos: 0,
- measure: null, measuredSomething: false, cm: cm,
- copyWidgets: copyWidgets};
-
- do {
- if (line.text) empty = false;
- builder.measure = line == realLine && measure;
+ // Iterate over the logical lines that make up this visual line.
+ for (var i = 0; i <= (lineView.rest ? lineView.rest.length : 0); i++) {
+ var line = i ? lineView.rest[i - 1] : lineView.line, order;
builder.pos = 0;
- builder.addToken = builder.measure ? buildTokenMeasure : buildToken;
- if ((old_ie || webkit) && cm.getOption("lineWrapping"))
+ builder.addToken = buildToken;
+ // Optionally wire in some hacks into the token-rendering
+ // algorithm, to deal with browser quirks.
+ if ((ie || webkit) && cm.getOption("lineWrapping"))
builder.addToken = buildTokenSplitSpaces(builder.addToken);
- var next = insertLineContent(line, builder, getLineStyles(cm, line));
- if (measure && line == realLine && !builder.measuredSomething) {
- measure[0] = builder.pre.appendChild(zeroWidthElement(cm.display.measure));
- builder.measuredSomething = true;
- }
- if (next) line = getLine(cm.doc, next.to.line);
- } while (next);
+ if (hasBadBidiRects(cm.display.measure) && (order = getOrder(line)))
+ builder.addToken = buildTokenBadBidi(builder.addToken, order);
+ builder.map = [];
+ insertLineContent(line, builder, getLineStyles(cm, line));
- if (measure && !builder.measuredSomething && !measure[0])
- measure[0] = builder.pre.appendChild(empty ? elt("span", "\u00a0") : zeroWidthElement(cm.display.measure));
- if (!builder.pre.firstChild && !lineIsHidden(cm.doc, realLine))
- builder.pre.appendChild(document.createTextNode("\u00a0"));
+ // Ensure at least a single node is present, for measuring.
+ if (builder.map.length == 0)
+ builder.map.push(0, 0, builder.content.appendChild(zeroWidthElement(cm.display.measure)));
- var order;
- // Work around problem with the reported dimensions of single-char
- // direction spans on IE (issue #1129). See also the comment in
- // cursorCoords.
- if (measure && ie && (order = getOrder(line))) {
- var l = order.length - 1;
- if (order[l].from == order[l].to) --l;
- var last = order[l], prev = order[l - 1];
- if (last.from + 1 == last.to && prev && last.level < prev.level) {
- var span = measure[builder.pos - 1];
- if (span) span.parentNode.insertBefore(span.measureRight = zeroWidthElement(cm.display.measure),
- span.nextSibling);
+ // Store the map and a cache object for the current logical line
+ if (i == 0) {
+ lineView.measure.map = builder.map;
+ lineView.measure.cache = {};
+ } else {
+ (lineView.measure.maps || (lineView.measure.maps = [])).push(builder.map);
+ (lineView.measure.caches || (lineView.measure.caches = [])).push({});
}
}
- var textClass = builder.textClass ? builder.textClass + " " + (realLine.textClass || "") : realLine.textClass;
- if (textClass) builder.pre.className = textClass;
-
- signal(cm, "renderLine", cm, realLine, builder.pre);
+ signal(cm, "renderLine", cm, lineView.line, builder.pre);
return builder;
}
@@ -4556,12 +5599,17 @@
return token;
}
+ // Build up the DOM representation for a single token, and add it to
+ // the line map. Takes care to render special characters separately.
function buildToken(builder, text, style, startStyle, endStyle, title) {
if (!text) return;
- var special = builder.cm.options.specialChars;
+ var special = builder.cm.options.specialChars, mustWrap = false;
if (!special.test(text)) {
builder.col += text.length;
var content = document.createTextNode(text);
+ builder.map.push(builder.pos, builder.pos + text.length, content);
+ if (ie_upto8) mustWrap = true;
+ builder.pos += text.length;
} else {
var content = document.createDocumentFragment(), pos = 0;
while (true) {
@@ -4569,56 +5617,38 @@
var m = special.exec(text);
var skipped = m ? m.index - pos : text.length - pos;
if (skipped) {
- content.appendChild(document.createTextNode(text.slice(pos, pos + skipped)));
+ var txt = document.createTextNode(text.slice(pos, pos + skipped));
+ if (ie_upto8) content.appendChild(elt("span", [txt]));
+ else content.appendChild(txt);
+ builder.map.push(builder.pos, builder.pos + skipped, txt);
builder.col += skipped;
+ builder.pos += skipped;
}
if (!m) break;
pos += skipped + 1;
if (m[0] == "\t") {
var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize;
- content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab"));
+ var txt = content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab"));
builder.col += tabWidth;
} else {
- var token = builder.cm.options.specialCharPlaceholder(m[0]);
- content.appendChild(token);
+ var txt = builder.cm.options.specialCharPlaceholder(m[0]);
+ if (ie_upto8) content.appendChild(elt("span", [txt]));
+ else content.appendChild(txt);
builder.col += 1;
}
+ builder.map.push(builder.pos, builder.pos + 1, txt);
+ builder.pos++;
}
}
- if (style || startStyle || endStyle || builder.measure) {
+ if (style || startStyle || endStyle || mustWrap) {
var fullStyle = style || "";
if (startStyle) fullStyle += startStyle;
if (endStyle) fullStyle += endStyle;
var token = elt("span", [content], fullStyle);
if (title) token.title = title;
- return builder.pre.appendChild(token);
+ return builder.content.appendChild(token);
}
- builder.pre.appendChild(content);
- }
-
- function buildTokenMeasure(builder, text, style, startStyle, endStyle) {
- var wrapping = builder.cm.options.lineWrapping;
- for (var i = 0; i < text.length; ++i) {
- var start = i == 0, to = i + 1;
- while (to < text.length && isExtendingChar(text.charAt(to))) ++to;
- var ch = text.slice(i, to);
- i = to - 1;
- if (i && wrapping && spanAffectsWrapping(text, i))
- builder.pre.appendChild(elt("wbr"));
- var old = builder.measure[builder.pos];
- var span = builder.measure[builder.pos] =
- buildToken(builder, ch, style,
- start && startStyle, i == text.length - 1 && endStyle);
- if (old) span.leftSide = old.leftSide || old;
- // In IE single-space nodes wrap differently than spaces
- // embedded in larger text nodes, except when set to
- // white-space: normal (issue #1268).
- if (old_ie && wrapping && ch == " " && i && !/\s/.test(text.charAt(i - 1)) &&
- i < text.length - 1 && !/\s/.test(text.charAt(i + 1)))
- span.style.whiteSpace = "normal";
- builder.pos += ch.length;
- }
- if (text.length) builder.measuredSomething = true;
+ builder.content.appendChild(content);
}
function buildTokenSplitSpaces(inner) {
@@ -4629,29 +5659,36 @@
return out;
}
return function(builder, text, style, startStyle, endStyle, title) {
- return inner(builder, text.replace(/ {3,}/g, split), style, startStyle, endStyle, title);
+ inner(builder, text.replace(/ {3,}/g, split), style, startStyle, endStyle, title);
+ };
+ }
+
+ // Work around nonsense dimensions being reported for stretches of
+ // right-to-left text.
+ function buildTokenBadBidi(inner, order) {
+ return function(builder, text, style, startStyle, endStyle, title) {
+ style = style ? style + " cm-force-border" : "cm-force-border";
+ var start = builder.pos, end = start + text.length;
+ for (;;) {
+ // Find the part that overlaps with the start of this text
+ for (var i = 0; i < order.length; i++) {
+ var part = order[i];
+ if (part.to > start && part.from <= start) break;
+ }
+ if (part.to >= end) return inner(builder, text, style, startStyle, endStyle, title);
+ inner(builder, text.slice(0, part.to - start), style, startStyle, null, title);
+ startStyle = null;
+ text = text.slice(part.to - start);
+ start = part.to;
+ }
};
}
function buildCollapsedSpan(builder, size, marker, ignoreWidget) {
- var widget = !ignoreWidget && marker.replacedWith;
+ var widget = !ignoreWidget && marker.widgetNode;
if (widget) {
- if (builder.copyWidgets) widget = widget.cloneNode(true);
- builder.pre.appendChild(widget);
- if (builder.measure) {
- if (size) {
- builder.measure[builder.pos] = widget;
- } else {
- var elt = zeroWidthElement(builder.cm.display.measure);
- if (marker.type == "bookmark" && !marker.insertLeft)
- builder.measure[builder.pos] = builder.pre.appendChild(elt);
- else if (builder.measure[builder.pos])
- return;
- else
- builder.measure[builder.pos] = builder.pre.insertBefore(elt, widget);
- }
- builder.measuredSomething = true;
- }
+ builder.map.push(builder.pos, builder.pos + size, widget);
+ builder.content.appendChild(widget);
}
builder.pos += size;
}
@@ -4686,12 +5723,12 @@
} else if (sp.from > pos && nextChange > sp.from) {
nextChange = sp.from;
}
- if (m.type == "bookmark" && sp.from == pos && m.replacedWith) foundBookmarks.push(m);
+ if (m.type == "bookmark" && sp.from == pos && m.widgetNode) foundBookmarks.push(m);
}
if (collapsed && (collapsed.from || 0) == pos) {
- buildCollapsedSpan(builder, (collapsed.to == null ? len : collapsed.to) - pos,
+ buildCollapsedSpan(builder, (collapsed.to == null ? len + 1 : collapsed.to) - pos,
collapsed.marker, collapsed.from == null);
- if (collapsed.to == null) return collapsed.marker.find();
+ if (collapsed.to == null) return;
}
if (!collapsed && foundBookmarks.length) for (var j = 0; j < foundBookmarks.length; ++j)
buildCollapsedSpan(builder, 0, foundBookmarks[j]);
@@ -4719,7 +5756,16 @@
// DOCUMENT DATA STRUCTURE
- function updateDoc(doc, change, markedSpans, selAfter, estimateHeight) {
+ // By default, updates that start and end at the beginning of a line
+ // are treated specially, in order to make the association of line
+ // widgets and marker elements with the text behave more intuitive.
+ function isWholeLineUpdate(doc, change) {
+ return change.from.ch == 0 && change.to.ch == 0 && lst(change.text) == "" &&
+ (!doc.cm || doc.cm.options.wholeLineUpdateBefore);
+ }
+
+ // Perform a change on the document data structure.
+ function updateDoc(doc, change, markedSpans, estimateHeight) {
function spansFor(n) {return markedSpans ? markedSpans[n] : null;}
function update(line, text, spans) {
updateLine(line, text, spans, estimateHeight);
@@ -4730,12 +5776,11 @@
var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line);
var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line;
- // First adjust the line structure
- if (from.ch == 0 && to.ch == 0 && lastText == "" &&
- (!doc.cm || doc.cm.options.wholeLineUpdateBefore)) {
+ // Adjust the line structure
+ if (isWholeLineUpdate(doc, change)) {
// This is a whole-line replace. Treated specially to make
// sure line objects move the way they are supposed to.
- for (var i = 0, e = text.length - 1, added = []; i < e; ++i)
+ for (var i = 0, added = []; i < text.length - 1; ++i)
added.push(new Line(text[i], spansFor(i), estimateHeight));
update(lastLine, lastLine.text, lastSpans);
if (nlines) doc.remove(from.line, nlines);
@@ -4744,7 +5789,7 @@
if (text.length == 1) {
update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans);
} else {
- for (var added = [], i = 1, e = text.length - 1; i < e; ++i)
+ for (var added = [], i = 1; i < text.length - 1; ++i)
added.push(new Line(text[i], spansFor(i), estimateHeight));
added.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight));
update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0));
@@ -4756,20 +5801,32 @@
} else {
update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0));
update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans);
- for (var i = 1, e = text.length - 1, added = []; i < e; ++i)
+ for (var i = 1, added = []; i < text.length - 1; ++i)
added.push(new Line(text[i], spansFor(i), estimateHeight));
if (nlines > 1) doc.remove(from.line + 1, nlines - 1);
doc.insert(from.line + 1, added);
}
signalLater(doc, "change", doc, change);
- setSelection(doc, selAfter.anchor, selAfter.head, null, true);
}
+ // The document is represented as a BTree consisting of leaves, with
+ // chunk of lines in them, and branches, with up to ten leaves or
+ // other branch nodes below them. The top node is always a branch
+ // node, and is the document object itself (meaning it has
+ // additional methods and properties).
+ //
+ // All nodes have parent links. The tree is used both to go from
+ // line numbers to line objects, and to go from objects to numbers.
+ // It also indexes by height, and is used to convert between height
+ // and line object, and to find the total height of the document.
+ //
+ // See also http://marijnhaverbeke.nl/blog/codemirror-line-tree.html
+
function LeafChunk(lines) {
this.lines = lines;
this.parent = null;
- for (var i = 0, e = lines.length, height = 0; i < e; ++i) {
+ for (var i = 0, height = 0; i < lines.length; ++i) {
lines[i].parent = this;
height += lines[i].height;
}
@@ -4778,6 +5835,7 @@
LeafChunk.prototype = {
chunkSize: function() { return this.lines.length; },
+ // Remove the n lines at offset 'at'.
removeInner: function(at, n) {
for (var i = at, e = at + n; i < e; ++i) {
var line = this.lines[i];
@@ -4787,14 +5845,18 @@
}
this.lines.splice(at, n);
},
+ // Helper used to collapse a small branch into a single leaf.
collapse: function(lines) {
- lines.splice.apply(lines, [lines.length, 0].concat(this.lines));
+ lines.push.apply(lines, this.lines);
},
+ // Insert the given array of lines at offset 'at', count them as
+ // having the given height.
insertInner: function(at, lines, height) {
this.height += height;
this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at));
- for (var i = 0, e = lines.length; i < e; ++i) lines[i].parent = this;
+ for (var i = 0; i < lines.length; ++i) lines[i].parent = this;
},
+ // Used to iterate over a part of the tree.
iterN: function(at, n, op) {
for (var e = at + n; at < e; ++at)
if (op(this.lines[at])) return true;
@@ -4804,7 +5866,7 @@
function BranchChunk(children) {
this.children = children;
var size = 0, height = 0;
- for (var i = 0, e = children.length; i < e; ++i) {
+ for (var i = 0; i < children.length; ++i) {
var ch = children[i];
size += ch.chunkSize(); height += ch.height;
ch.parent = this;
@@ -4829,7 +5891,10 @@
at = 0;
} else at -= sz;
}
- if (this.size - n < 25) {
+ // If the result is smaller than 25 lines, ensure that it is a
+ // single leaf node.
+ if (this.size - n < 25 &&
+ (this.children.length > 1 || !(this.children[0] instanceof LeafChunk))) {
var lines = [];
this.collapse(lines);
this.children = [new LeafChunk(lines)];
@@ -4837,12 +5902,12 @@
}
},
collapse: function(lines) {
- for (var i = 0, e = this.children.length; i < e; ++i) this.children[i].collapse(lines);
+ for (var i = 0; i < this.children.length; ++i) this.children[i].collapse(lines);
},
insertInner: function(at, lines, height) {
this.size += lines.length;
this.height += height;
- for (var i = 0, e = this.children.length; i < e; ++i) {
+ for (var i = 0; i < this.children.length; ++i) {
var child = this.children[i], sz = child.chunkSize();
if (at <= sz) {
child.insertInner(at, lines, height);
@@ -4861,6 +5926,7 @@
at -= sz;
}
},
+ // When a node has grown, check whether it should be split.
maybeSpill: function() {
if (this.children.length <= 10) return;
var me = this;
@@ -4883,7 +5949,7 @@
me.parent.maybeSpill();
},
iterN: function(at, n, op) {
- for (var i = 0, e = this.children.length; i < e; ++i) {
+ for (var i = 0; i < this.children.length; ++i) {
var child = this.children[i], sz = child.chunkSize();
if (at < sz) {
var used = Math.min(n, sz - at);
@@ -4904,43 +5970,52 @@
this.first = firstLine;
this.scrollTop = this.scrollLeft = 0;
this.cantEdit = false;
- this.history = makeHistory();
this.cleanGeneration = 1;
this.frontier = firstLine;
var start = Pos(firstLine, 0);
- this.sel = {from: start, to: start, head: start, anchor: start, shift: false, extend: false, goalColumn: null};
+ this.sel = simpleSelection(start);
+ this.history = new History(null);
this.id = ++nextDocId;
this.modeOption = mode;
if (typeof text == "string") text = splitLines(text);
- updateDoc(this, {from: start, to: start, text: text}, null, {head: start, anchor: start});
+ updateDoc(this, {from: start, to: start, text: text});
+ setSelection(this, simpleSelection(start), sel_dontScroll);
};
Doc.prototype = createObj(BranchChunk.prototype, {
constructor: Doc,
+ // Iterate over the document. Supports two forms -- with only one
+ // argument, it calls that for each line in the document. With
+ // three, it iterates over the range given by the first two (with
+ // the second being non-inclusive).
iter: function(from, to, op) {
if (op) this.iterN(from - this.first, to - from, op);
else this.iterN(this.first, this.first + this.size, from);
},
+ // Non-public interface for adding and removing lines.
insert: function(at, lines) {
var height = 0;
- for (var i = 0, e = lines.length; i < e; ++i) height += lines[i].height;
+ for (var i = 0; i < lines.length; ++i) height += lines[i].height;
this.insertInner(at - this.first, lines, height);
},
remove: function(at, n) { this.removeInner(at - this.first, n); },
+ // From here, the methods are part of the public interface. Most
+ // are also available from CodeMirror (editor) instances.
+
getValue: function(lineSep) {
var lines = getLines(this, this.first, this.first + this.size);
if (lineSep === false) return lines;
return lines.join(lineSep || "\n");
},
- setValue: function(code) {
+ setValue: docMethodOp(function(code) {
var top = Pos(this.first, 0), last = this.first + this.size - 1;
makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length),
- text: splitLines(code), origin: "setValue"},
- {head: top, anchor: top}, true);
- },
+ text: splitLines(code), origin: "setValue"}, true);
+ setSelection(this, simpleSelection(top));
+ }),
replaceRange: function(code, from, to, origin) {
from = clipPos(this, from);
to = to ? clipPos(this, to) : from;
@@ -4953,21 +6028,13 @@
},
getLine: function(line) {var l = this.getLineHandle(line); return l && l.text;},
- setLine: function(line, text) {
- if (isLine(this, line))
- replaceRange(this, text, Pos(line, 0), clipPos(this, Pos(line)));
- },
- removeLine: function(line) {
- if (line) replaceRange(this, "", clipPos(this, Pos(line - 1)), clipPos(this, Pos(line)));
- else replaceRange(this, "", Pos(0, 0), clipPos(this, Pos(1, 0)));
- },
getLineHandle: function(line) {if (isLine(this, line)) return getLine(this, line);},
getLineNumber: function(line) {return lineNo(line);},
getLineHandleVisualStart: function(line) {
if (typeof line == "number") line = getLine(this, line);
- return visualLine(this, line);
+ return visualLine(line);
},
lineCount: function() {return this.size;},
@@ -4977,41 +6044,96 @@
clipPos: function(pos) {return clipPos(this, pos);},
getCursor: function(start) {
- var sel = this.sel, pos;
- if (start == null || start == "head") pos = sel.head;
- else if (start == "anchor") pos = sel.anchor;
- else if (start == "end" || start === false) pos = sel.to;
- else pos = sel.from;
- return copyPos(pos);
+ var range = this.sel.primary(), pos;
+ if (start == null || start == "head") pos = range.head;
+ else if (start == "anchor") pos = range.anchor;
+ else if (start == "end" || start == "to" || start === false) pos = range.to();
+ else pos = range.from();
+ return pos;
},
- somethingSelected: function() {return !posEq(this.sel.head, this.sel.anchor);},
+ listSelections: function() { return this.sel.ranges; },
+ somethingSelected: function() {return this.sel.somethingSelected();},
- setCursor: docOperation(function(line, ch, extend) {
- var pos = clipPos(this, typeof line == "number" ? Pos(line, ch || 0) : line);
- if (extend) extendSelection(this, pos);
- else setSelection(this, pos, pos);
+ setCursor: docMethodOp(function(line, ch, options) {
+ setSimpleSelection(this, clipPos(this, typeof line == "number" ? Pos(line, ch || 0) : line), null, options);
}),
- setSelection: docOperation(function(anchor, head, bias) {
- setSelection(this, clipPos(this, anchor), clipPos(this, head || anchor), bias);
+ setSelection: docMethodOp(function(anchor, head, options) {
+ setSimpleSelection(this, clipPos(this, anchor), clipPos(this, head || anchor), options);
}),
- extendSelection: docOperation(function(from, to, bias) {
- extendSelection(this, clipPos(this, from), to && clipPos(this, to), bias);
+ extendSelection: docMethodOp(function(head, other, options) {
+ extendSelection(this, clipPos(this, head), other && clipPos(this, other), options);
+ }),
+ extendSelections: docMethodOp(function(heads, options) {
+ extendSelections(this, clipPosArray(this, heads, options));
+ }),
+ extendSelectionsBy: docMethodOp(function(f, options) {
+ extendSelections(this, map(this.sel.ranges, f), options);
+ }),
+ setSelections: docMethodOp(function(ranges, primary, options) {
+ if (!ranges.length) return;
+ for (var i = 0, out = []; i < ranges.length; i++)
+ out[i] = new Range(clipPos(this, ranges[i].anchor),
+ clipPos(this, ranges[i].head));
+ if (primary == null) primary = Math.min(ranges.length - 1, this.sel.primIndex);
+ setSelection(this, normalizeSelection(out, primary), options);
+ }),
+ addSelection: docMethodOp(function(anchor, head, options) {
+ var ranges = this.sel.ranges.slice(0);
+ ranges.push(new Range(clipPos(this, anchor), clipPos(this, head || anchor)));
+ setSelection(this, normalizeSelection(ranges, ranges.length - 1), options);
}),
- getSelection: function(lineSep) {return this.getRange(this.sel.from, this.sel.to, lineSep);},
- replaceSelection: function(code, collapse, origin) {
- makeChange(this, {from: this.sel.from, to: this.sel.to, text: splitLines(code), origin: origin}, collapse || "around");
+ getSelection: function(lineSep) {
+ var ranges = this.sel.ranges, lines;
+ for (var i = 0; i < ranges.length; i++) {
+ var sel = getBetween(this, ranges[i].from(), ranges[i].to());
+ lines = lines ? lines.concat(sel) : sel;
+ }
+ if (lineSep === false) return lines;
+ else return lines.join(lineSep || "\n");
},
- undo: docOperation(function() {makeChangeFromHistory(this, "undo");}),
- redo: docOperation(function() {makeChangeFromHistory(this, "redo");}),
+ getSelections: function(lineSep) {
+ var parts = [], ranges = this.sel.ranges;
+ for (var i = 0; i < ranges.length; i++) {
+ var sel = getBetween(this, ranges[i].from(), ranges[i].to());
+ if (lineSep !== false) sel = sel.join(lineSep || "\n");
+ parts[i] = sel;
+ }
+ return parts;
+ },
+ replaceSelection: docMethodOp(function(code, collapse, origin) {
+ var dup = [];
+ for (var i = 0; i < this.sel.ranges.length; i++)
+ dup[i] = code;
+ this.replaceSelections(dup, collapse, origin || "+input");
+ }),
+ replaceSelections: function(code, collapse, origin) {
+ var changes = [], sel = this.sel;
+ for (var i = 0; i < sel.ranges.length; i++) {
+ var range = sel.ranges[i];
+ changes[i] = {from: range.from(), to: range.to(), text: splitLines(code[i]), origin: origin};
+ }
+ var newSel = collapse && collapse != "end" && computeReplacedSel(this, changes, collapse);
+ for (var i = changes.length - 1; i >= 0; i--)
+ makeChange(this, changes[i]);
+ if (newSel) setSelectionReplaceHistory(this, newSel);
+ else if (this.cm) ensureCursorVisible(this.cm);
+ },
+ undo: docMethodOp(function() {makeChangeFromHistory(this, "undo");}),
+ redo: docMethodOp(function() {makeChangeFromHistory(this, "redo");}),
+ undoSelection: docMethodOp(function() {makeChangeFromHistory(this, "undo", true);}),
+ redoSelection: docMethodOp(function() {makeChangeFromHistory(this, "redo", true);}),
- setExtending: function(val) {this.sel.extend = val;},
+ setExtending: function(val) {this.extend = val;},
+ getExtending: function() {return this.extend;},
historySize: function() {
- var hist = this.history;
- return {undo: hist.done.length, redo: hist.undone.length};
+ var hist = this.history, done = 0, undone = 0;
+ for (var i = 0; i < hist.done.length; i++) if (!hist.done[i].ranges) ++done;
+ for (var i = 0; i < hist.undone.length; i++) if (!hist.undone[i].ranges) ++undone;
+ return {undo: done, redo: undone};
},
- clearHistory: function() {this.history = makeHistory(this.history.maxGeneration);},
+ clearHistory: function() {this.history = new History(this.history.maxGeneration);},
markClean: function() {
this.cleanGeneration = this.changeGeneration(true);
@@ -5030,9 +6152,9 @@
undone: copyHistoryArray(this.history.undone)};
},
setHistory: function(histData) {
- var hist = this.history = makeHistory(this.history.maxGeneration);
- hist.done = histData.done.slice(0);
- hist.undone = histData.undone.slice(0);
+ var hist = this.history = new History(this.history.maxGeneration);
+ hist.done = copyHistoryArray(histData.done.slice(0), null, true);
+ hist.undone = copyHistoryArray(histData.undone.slice(0), null, true);
},
markText: function(from, to, options) {
@@ -5041,7 +6163,7 @@
setBookmark: function(pos, options) {
var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options),
insertLeft: options && options.insertLeft,
- clearWhenEmpty: false};
+ clearWhenEmpty: false, shared: options && options.shared};
pos = clipPos(this, pos);
return markText(this, pos, pos, realOpts, "bookmark");
},
@@ -5056,6 +6178,22 @@
}
return markers;
},
+ findMarks: function(from, to) {
+ from = clipPos(this, from); to = clipPos(this, to);
+ var found = [], lineNo = from.line;
+ this.iter(from.line, to.line + 1, function(line) {
+ var spans = line.markedSpans;
+ if (spans) for (var i = 0; i < spans.length; i++) {
+ var span = spans[i];
+ if (!(lineNo == from.line && from.ch > span.to ||
+ span.from == null && lineNo != from.line||
+ lineNo == to.line && span.from > to.ch))
+ found.push(span.marker.parent || span.marker);
+ }
+ ++lineNo;
+ });
+ return found;
+ },
getAllMarks: function() {
var markers = [];
this.iter(function(line) {
@@ -5089,8 +6227,8 @@
copy: function(copyHistory) {
var doc = new Doc(getLines(this, this.first, this.first + this.size), this.modeOption, this.first);
doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft;
- doc.sel = {from: this.sel.from, to: this.sel.to, head: this.sel.head, anchor: this.sel.anchor,
- shift: this.sel.shift, extend: false, goalColumn: this.sel.goalColumn};
+ doc.sel = this.sel;
+ doc.extend = false;
if (copyHistory) {
doc.history.undoDepth = this.history.undoDepth;
doc.setHistory(this.getHistory());
@@ -5122,7 +6260,7 @@
if (other.history == this.history) {
var splitIds = [other.id];
linkedDocs(other, function(doc) {splitIds.push(doc.id);}, true);
- other.history = makeHistory();
+ other.history = new History(null);
other.history.done = copyHistoryArray(this.history.done, splitIds);
other.history.undone = copyHistoryArray(this.history.undone, splitIds);
}
@@ -5133,9 +6271,10 @@
getEditor: function() {return this.cm;}
});
+ // Public alias.
Doc.prototype.eachLine = Doc.prototype.iter;
- // The Doc methods that should be available on CodeMirror instances
+ // Set up methods on CodeMirror's prototype to redirect to the editor's document.
var dontDelegate = "iter insert remove copy getEditor".split(" ");
for (var prop in Doc.prototype) if (Doc.prototype.hasOwnProperty(prop) && indexOf(dontDelegate, prop) < 0)
CodeMirror.prototype[prop] = (function(method) {
@@ -5144,6 +6283,7 @@
eventMixin(Doc);
+ // Call f for all linked documents.
function linkedDocs(doc, f, sharedHistOnly) {
function propagate(doc, skip, sharedHist) {
if (doc.linked) for (var i = 0; i < doc.linked.length; ++i) {
@@ -5158,22 +6298,25 @@
propagate(doc, null, true);
}
+ // Attach a document to an editor.
function attachDoc(cm, doc) {
if (doc.cm) throw new Error("This document is already in use.");
cm.doc = doc;
doc.cm = cm;
estimateLineHeights(cm);
loadMode(cm);
- if (!cm.options.lineWrapping) computeMaxLength(cm);
+ if (!cm.options.lineWrapping) findMaxLine(cm);
cm.options.mode = doc.modeOption;
regChange(cm);
}
// LINE UTILITIES
- function getLine(chunk, n) {
- n -= chunk.first;
- while (!chunk.lines) {
+ // Find the line object corresponding to the given line number.
+ function getLine(doc, n) {
+ n -= doc.first;
+ if (n < 0 || n >= doc.size) throw new Error("There is no line " + (n + doc.first) + " in the document.");
+ for (var chunk = doc; !chunk.lines;) {
for (var i = 0;; ++i) {
var child = chunk.children[i], sz = child.chunkSize();
if (n < sz) { chunk = child; break; }
@@ -5183,6 +6326,8 @@
return chunk.lines[n];
}
+ // Get the part of a document between two positions, as an array of
+ // strings.
function getBetween(doc, start, end) {
var out = [], n = start.line;
doc.iter(start.line, end.line + 1, function(line) {
@@ -5194,17 +6339,22 @@
});
return out;
}
+ // Get the lines between from and to, as array of strings.
function getLines(doc, from, to) {
var out = [];
doc.iter(from, to, function(line) { out.push(line.text); });
return out;
}
+ // Update the height of a line, propagating the height change
+ // upwards to parent nodes.
function updateLineHeight(line, height) {
var diff = height - line.height;
- for (var n = line; n; n = n.parent) n.height += diff;
+ if (diff) for (var n = line; n; n = n.parent) n.height += diff;
}
+ // Given a line object, find its line number by walking up through
+ // its parent links.
function lineNo(line) {
if (line.parent == null) return null;
var cur = line.parent, no = indexOf(cur.lines, line);
@@ -5217,10 +6367,12 @@
return no + cur.first;
}
+ // Find the line at the given vertical position, using the height
+ // information in the document tree.
function lineAtHeight(chunk, h) {
var n = chunk.first;
outer: do {
- for (var i = 0, e = chunk.children.length; i < e; ++i) {
+ for (var i = 0; i < chunk.children.length; ++i) {
var child = chunk.children[i], ch = child.height;
if (h < ch) { chunk = child; continue outer; }
h -= ch;
@@ -5228,7 +6380,7 @@
}
return n;
} while (!chunk.lines);
- for (var i = 0, e = chunk.lines.length; i < e; ++i) {
+ for (var i = 0; i < chunk.lines.length; ++i) {
var line = chunk.lines[i], lh = line.height;
if (h < lh) break;
h -= lh;
@@ -5236,8 +6388,10 @@
return n + i;
}
- function heightAtLine(cm, lineObj) {
- lineObj = visualLine(cm.doc, lineObj);
+
+ // Find the height above the given line.
+ function heightAtLine(lineObj) {
+ lineObj = visualLine(lineObj);
var h = 0, chunk = lineObj.parent;
for (var i = 0; i < chunk.lines.length; ++i) {
@@ -5255,6 +6409,9 @@
return h;
}
+ // Get the bidi ordering for the given line (and cache it). Returns
+ // false for lines that are fully left-to-right, and an array of
+ // BidiSpan objects otherwise.
function getOrder(line) {
var order = line.order;
if (order == null) order = line.order = bidiOrdering(line.text);
@@ -5263,20 +6420,141 @@
// HISTORY
- function makeHistory(startGen) {
- return {
- // Arrays of history events. Doing something adds an event to
- // done and clears undo. Undoing moves events from done to
- // undone, redoing moves them in the other direction.
- done: [], undone: [], undoDepth: Infinity,
- // Used to track when changes can be merged into a single undo
- // event
- lastTime: 0, lastOp: null, lastOrigin: null,
- // Used by the isClean() method
- generation: startGen || 1, maxGeneration: startGen || 1
- };
+ function History(startGen) {
+ // Arrays of change events and selections. Doing something adds an
+ // event to done and clears undo. Undoing moves events from done
+ // to undone, redoing moves them in the other direction.
+ this.done = []; this.undone = [];
+ this.undoDepth = Infinity;
+ // Used to track when changes can be merged into a single undo
+ // event
+ this.lastModTime = this.lastSelTime = 0;
+ this.lastOp = null;
+ this.lastOrigin = this.lastSelOrigin = null;
+ // Used by the isClean() method
+ this.generation = this.maxGeneration = startGen || 1;
}
+ // Create a history change event from an updateDoc-style change
+ // object.
+ function historyChangeFromChange(doc, change) {
+ var histChange = {from: copyPos(change.from), to: changeEnd(change), text: getBetween(doc, change.from, change.to)};
+ attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);
+ linkedDocs(doc, function(doc) {attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);}, true);
+ return histChange;
+ }
+
+ // Pop all selection events off the end of a history array. Stop at
+ // a change event.
+ function clearSelectionEvents(array) {
+ while (array.length) {
+ var last = lst(array);
+ if (last.ranges) array.pop();
+ else break;
+ }
+ }
+
+ // Find the top change event in the history. Pop off selection
+ // events that are in the way.
+ function lastChangeEvent(hist, force) {
+ if (force) {
+ clearSelectionEvents(hist.done);
+ return lst(hist.done);
+ } else if (hist.done.length && !lst(hist.done).ranges) {
+ return lst(hist.done);
+ } else if (hist.done.length > 1 && !hist.done[hist.done.length - 2].ranges) {
+ hist.done.pop();
+ return lst(hist.done);
+ }
+ }
+
+ // Register a change in the history. Merges changes that are within
+ // a single operation, ore are close together with an origin that
+ // allows merging (starting with "+") into a single event.
+ function addChangeToHistory(doc, change, selAfter, opId) {
+ var hist = doc.history;
+ hist.undone.length = 0;
+ var time = +new Date, cur;
+
+ if ((hist.lastOp == opId ||
+ hist.lastOrigin == change.origin && change.origin &&
+ ((change.origin.charAt(0) == "+" && doc.cm && hist.lastModTime > time - doc.cm.options.historyEventDelay) ||
+ change.origin.charAt(0) == "*")) &&
+ (cur = lastChangeEvent(hist, hist.lastOp == opId))) {
+ // Merge this change into the last event
+ var last = lst(cur.changes);
+ if (cmp(change.from, change.to) == 0 && cmp(change.from, last.to) == 0) {
+ // Optimized case for simple insertion -- don't want to add
+ // new changesets for every character typed
+ last.to = changeEnd(change);
+ } else {
+ // Add new sub-event
+ cur.changes.push(historyChangeFromChange(doc, change));
+ }
+ } else {
+ // Can not be merged, start a new event.
+ var before = lst(hist.done);
+ if (!before || !before.ranges)
+ pushSelectionToHistory(doc.sel, hist.done);
+ cur = {changes: [historyChangeFromChange(doc, change)],
+ generation: hist.generation};
+ hist.done.push(cur);
+ while (hist.done.length > hist.undoDepth) {
+ hist.done.shift();
+ if (!hist.done[0].ranges) hist.done.shift();
+ }
+ }
+ hist.done.push(selAfter);
+ hist.generation = ++hist.maxGeneration;
+ hist.lastModTime = hist.lastSelTime = time;
+ hist.lastOp = opId;
+ hist.lastOrigin = hist.lastSelOrigin = change.origin;
+
+ if (!last) signal(doc, "historyAdded");
+ }
+
+ function selectionEventCanBeMerged(doc, origin, prev, sel) {
+ var ch = origin.charAt(0);
+ return ch == "*" ||
+ ch == "+" &&
+ prev.ranges.length == sel.ranges.length &&
+ prev.somethingSelected() == sel.somethingSelected() &&
+ new Date - doc.history.lastSelTime <= (doc.cm ? doc.cm.options.historyEventDelay : 500);
+ }
+
+ // Called whenever the selection changes, sets the new selection as
+ // the pending selection in the history, and pushes the old pending
+ // selection into the 'done' array when it was significantly
+ // different (in number of selected ranges, emptiness, or time).
+ function addSelectionToHistory(doc, sel, opId, options) {
+ var hist = doc.history, origin = options && options.origin;
+
+ // A new event is started when the previous origin does not match
+ // the current, or the origins don't allow matching. Origins
+ // starting with * are always merged, those starting with + are
+ // merged when similar and close together in time.
+ if (opId == hist.lastOp ||
+ (origin && hist.lastSelOrigin == origin &&
+ (hist.lastModTime == hist.lastSelTime && hist.lastOrigin == origin ||
+ selectionEventCanBeMerged(doc, origin, lst(hist.done), sel))))
+ hist.done[hist.done.length - 1] = sel;
+ else
+ pushSelectionToHistory(sel, hist.done);
+
+ hist.lastSelTime = +new Date;
+ hist.lastSelOrigin = origin;
+ hist.lastOp = opId;
+ if (options && options.clearRedo !== false)
+ clearSelectionEvents(hist.undone);
+ }
+
+ function pushSelectionToHistory(sel, dest) {
+ var top = lst(dest);
+ if (!(top && top.ranges && top.equals(sel)))
+ dest.push(sel);
+ }
+
+ // Used to store marked span information in the history.
function attachLocalSpans(doc, change, from, to) {
var existing = change["spans_" + doc.id], n = 0;
doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), function(line) {
@@ -5286,51 +6564,8 @@
});
}
- function historyChangeFromChange(doc, change) {
- var from = { line: change.from.line, ch: change.from.ch };
- var histChange = {from: from, to: changeEnd(change), text: getBetween(doc, change.from, change.to)};
- attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);
- linkedDocs(doc, function(doc) {attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);}, true);
- return histChange;
- }
-
- function addToHistory(doc, change, selAfter, opId) {
- var hist = doc.history;
- hist.undone.length = 0;
- var time = +new Date, cur = lst(hist.done);
-
- if (cur &&
- (hist.lastOp == opId ||
- hist.lastOrigin == change.origin && change.origin &&
- ((change.origin.charAt(0) == "+" && doc.cm && hist.lastTime > time - doc.cm.options.historyEventDelay) ||
- change.origin.charAt(0) == "*"))) {
- // Merge this change into the last event
- var last = lst(cur.changes);
- if (posEq(change.from, change.to) && posEq(change.from, last.to)) {
- // Optimized case for simple insertion -- don't want to add
- // new changesets for every character typed
- last.to = changeEnd(change);
- } else {
- // Add new sub-event
- cur.changes.push(historyChangeFromChange(doc, change));
- }
- cur.anchorAfter = selAfter.anchor; cur.headAfter = selAfter.head;
- } else {
- // Can not be merged, start a new event.
- cur = {changes: [historyChangeFromChange(doc, change)],
- generation: hist.generation,
- anchorBefore: doc.sel.anchor, headBefore: doc.sel.head,
- anchorAfter: selAfter.anchor, headAfter: selAfter.head};
- hist.done.push(cur);
- while (hist.done.length > hist.undoDepth)
- hist.done.shift();
- }
- hist.generation = ++hist.maxGeneration;
- hist.lastTime = time;
- hist.lastOp = opId;
- hist.lastOrigin = change.origin;
- }
-
+ // When un/re-doing restores text containing marked spans, those
+ // that have been explicitly cleared should not be restored.
function removeClearedSpans(spans) {
if (!spans) return null;
for (var i = 0, out; i < spans.length; ++i) {
@@ -5340,6 +6575,7 @@
return !out ? spans : out.length ? out : null;
}
+ // Retrieve and filter the old marked spans stored in a change event.
function getOldSpans(doc, change) {
var found = change["spans_" + doc.id];
if (!found) return null;
@@ -5350,11 +6586,15 @@
// Used both to provide a JSON-safe object in .getHistory, and, when
// detaching a document, to split the history in two
- function copyHistoryArray(events, newGroup) {
+ function copyHistoryArray(events, newGroup, instantiateSel) {
for (var i = 0, copy = []; i < events.length; ++i) {
- var event = events[i], changes = event.changes, newChanges = [];
- copy.push({changes: newChanges, anchorBefore: event.anchorBefore, headBefore: event.headBefore,
- anchorAfter: event.anchorAfter, headAfter: event.headAfter});
+ var event = events[i];
+ if (event.ranges) {
+ copy.push(instantiateSel ? Selection.prototype.deepCopy.call(event) : event);
+ continue;
+ }
+ var changes = event.changes, newChanges = [];
+ copy.push({changes: newChanges});
for (var j = 0; j < changes.length; ++j) {
var change = changes[j], m;
newChanges.push({from: change.from, to: change.to, text: change.text});
@@ -5371,7 +6611,7 @@
// Rebasing/resetting history to deal with externally-sourced changes
- function rebaseHistSel(pos, from, to, diff) {
+ function rebaseHistSelSingle(pos, from, to, diff) {
if (to < pos.line) {
pos.line += diff;
} else if (from < pos.line) {
@@ -5390,28 +6630,27 @@
function rebaseHistArray(array, from, to, diff) {
for (var i = 0; i < array.length; ++i) {
var sub = array[i], ok = true;
+ if (sub.ranges) {
+ if (!sub.copied) { sub = array[i] = sub.deepCopy(); sub.copied = true; }
+ for (var j = 0; j < sub.ranges.length; j++) {
+ rebaseHistSelSingle(sub.ranges[j].anchor, from, to, diff);
+ rebaseHistSelSingle(sub.ranges[j].head, from, to, diff);
+ }
+ continue;
+ }
for (var j = 0; j < sub.changes.length; ++j) {
var cur = sub.changes[j];
- if (!sub.copied) { cur.from = copyPos(cur.from); cur.to = copyPos(cur.to); }
if (to < cur.from.line) {
- cur.from.line += diff;
- cur.to.line += diff;
+ cur.from = Pos(cur.from.line + diff, cur.from.ch);
+ cur.to = Pos(cur.to.line + diff, cur.to.ch);
} else if (from <= cur.to.line) {
ok = false;
break;
}
}
- if (!sub.copied) {
- sub.anchorBefore = copyPos(sub.anchorBefore); sub.headBefore = copyPos(sub.headBefore);
- sub.anchorAfter = copyPos(sub.anchorAfter); sub.readAfter = copyPos(sub.headAfter);
- sub.copied = true;
- }
if (!ok) {
array.splice(0, i + 1);
i = 0;
- } else {
- rebaseHistSel(sub.anchorBefore); rebaseHistSel(sub.headBefore);
- rebaseHistSel(sub.anchorAfter); rebaseHistSel(sub.headAfter);
}
}
}
@@ -5422,30 +6661,23 @@
rebaseHistArray(hist.undone, from, to, diff);
}
- // EVENT OPERATORS
+ // EVENT UTILITIES
- function stopMethod() {e_stop(this);}
- // Ensure an event has a stop method.
- function addStop(event) {
- if (!event.stop) event.stop = stopMethod;
- return event;
- }
+ // Due to the fact that we still support jurassic IE versions, some
+ // compatibility wrappers are needed.
- function e_preventDefault(e) {
+ var e_preventDefault = CodeMirror.e_preventDefault = function(e) {
if (e.preventDefault) e.preventDefault();
else e.returnValue = false;
- }
- function e_stopPropagation(e) {
+ };
+ var e_stopPropagation = CodeMirror.e_stopPropagation = function(e) {
if (e.stopPropagation) e.stopPropagation();
else e.cancelBubble = true;
- }
+ };
function e_defaultPrevented(e) {
return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == false;
}
- function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);}
- CodeMirror.e_stop = e_stop;
- CodeMirror.e_preventDefault = e_preventDefault;
- CodeMirror.e_stopPropagation = e_stopPropagation;
+ var e_stop = CodeMirror.e_stop = function(e) {e_preventDefault(e); e_stopPropagation(e);};
function e_target(e) {return e.target || e.srcElement;}
function e_button(e) {
@@ -5461,7 +6693,10 @@
// EVENT HANDLING
- function on(emitter, type, f) {
+ // Lightweight event framework. on/off also work on DOM nodes,
+ // registering native DOM handlers.
+
+ var on = CodeMirror.on = function(emitter, type, f) {
if (emitter.addEventListener)
emitter.addEventListener(type, f, false);
else if (emitter.attachEvent)
@@ -5471,9 +6706,9 @@
var arr = map[type] || (map[type] = []);
arr.push(f);
}
- }
+ };
- function off(emitter, type, f) {
+ var off = CodeMirror.off = function(emitter, type, f) {
if (emitter.removeEventListener)
emitter.removeEventListener(type, f, false);
else if (emitter.detachEvent)
@@ -5484,15 +6719,22 @@
for (var i = 0; i < arr.length; ++i)
if (arr[i] == f) { arr.splice(i, 1); break; }
}
- }
+ };
- function signal(emitter, type /*, values...*/) {
+ var signal = CodeMirror.signal = function(emitter, type /*, values...*/) {
var arr = emitter._handlers && emitter._handlers[type];
if (!arr) return;
var args = Array.prototype.slice.call(arguments, 2);
for (var i = 0; i < arr.length; ++i) arr[i].apply(null, args);
- }
+ };
+ // Often, we want to signal events at a point where we are in the
+ // middle of some work, but don't want the handler to start calling
+ // other methods on the editor, which might be in an inconsistent
+ // state or simply not expect any other events to happen.
+ // signalLater looks whether there are any handlers, and schedules
+ // them to be executed when the last operation ends, or, if no
+ // operation is active, when a timeout fires.
var delayedCallbacks, delayedCallbackDepth = 0;
function signalLater(emitter, type /*, values...*/) {
var arr = emitter._handlers && emitter._handlers[type];
@@ -5508,11 +6750,6 @@
delayedCallbacks.push(bnd(arr[i]));
}
- function signalDOMEvent(cm, e, override) {
- signal(cm, override || e.type, cm, e);
- return e_defaultPrevented(e) || e.codemirrorIgnore;
- }
-
function fireDelayed() {
--delayedCallbackDepth;
var delayed = delayedCallbacks;
@@ -5520,13 +6757,21 @@
for (var i = 0; i < delayed.length; ++i) delayed[i]();
}
+ // The DOM events that CodeMirror handles can be overridden by
+ // registering a (non-DOM) handler on the editor for the event name,
+ // and preventDefault-ing the event in that handler.
+ function signalDOMEvent(cm, e, override) {
+ signal(cm, override || e.type, cm, e);
+ return e_defaultPrevented(e) || e.codemirrorIgnore;
+ }
+
function hasHandler(emitter, type) {
var arr = emitter._handlers && emitter._handlers[type];
return arr && arr.length > 0;
}
- CodeMirror.on = on; CodeMirror.off = off; CodeMirror.signal = signal;
-
+ // Add on and off methods to a constructor's prototype, to make
+ // registering events on such objects more convenient.
function eventMixin(ctor) {
ctor.prototype.on = function(type, f) {on(this, type, f);};
ctor.prototype.off = function(type, f) {off(this, type, f);};
@@ -5541,23 +6786,47 @@
// handling this'.
var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}};
+ // Reused option objects for setSelection & friends
+ var sel_dontScroll = {scroll: false}, sel_mouse = {origin: "*mouse"}, sel_move = {origin: "+move"};
+
function Delayed() {this.id = null;}
- Delayed.prototype = {set: function(ms, f) {clearTimeout(this.id); this.id = setTimeout(f, ms);}};
+ Delayed.prototype.set = function(ms, f) {
+ clearTimeout(this.id);
+ this.id = setTimeout(f, ms);
+ };
// Counts the column offset in a string, taking tabs into account.
// Used mostly to find indentation.
- function countColumn(string, end, tabSize, startIndex, startValue) {
+ var countColumn = CodeMirror.countColumn = function(string, end, tabSize, startIndex, startValue) {
if (end == null) {
end = string.search(/[^\s\u00a0]/);
if (end == -1) end = string.length;
}
- for (var i = startIndex || 0, n = startValue || 0; i < end; ++i) {
- if (string.charAt(i) == "\t") n += tabSize - (n % tabSize);
- else ++n;
+ for (var i = startIndex || 0, n = startValue || 0;;) {
+ var nextTab = string.indexOf("\t", i);
+ if (nextTab < 0 || nextTab >= end)
+ return n + (end - i);
+ n += nextTab - i;
+ n += tabSize - (n % tabSize);
+ i = nextTab + 1;
}
- return n;
+ };
+
+ // The inverse of countColumn -- find the offset that corresponds to
+ // a particular column.
+ function findColumn(string, goal, tabSize) {
+ for (var pos = 0, col = 0;;) {
+ var nextTab = string.indexOf("\t", pos);
+ if (nextTab == -1) nextTab = string.length;
+ var skipped = nextTab - pos;
+ if (nextTab == string.length || col + skipped >= goal)
+ return pos + Math.min(skipped, goal - col);
+ col += nextTab - pos;
+ col += tabSize - (col % tabSize);
+ pos = nextTab + 1;
+ if (col >= goal) return pos;
+ }
}
- CodeMirror.countColumn = countColumn;
var spaceStrs = [""];
function spaceStr(n) {
@@ -5568,31 +6837,37 @@
function lst(arr) { return arr[arr.length-1]; }
- function selectInput(node) {
- if (ios) { // Mobile Safari apparently has a bug where select() is broken.
- node.selectionStart = 0;
- node.selectionEnd = node.value.length;
- } else {
- // Suppress mysterious IE10 errors
- try { node.select(); }
- catch(_e) {}
- }
- }
+ var selectInput = function(node) { node.select(); };
+ if (ios) // Mobile Safari apparently has a bug where select() is broken.
+ selectInput = function(node) { node.selectionStart = 0; node.selectionEnd = node.value.length; };
+ else if (ie) // Suppress mysterious IE10 errors
+ selectInput = function(node) { try { node.select(); } catch(_e) {} };
- function indexOf(collection, elt) {
- if (collection.indexOf) return collection.indexOf(elt);
- for (var i = 0, e = collection.length; i < e; ++i)
- if (collection[i] == elt) return i;
+ function indexOf(array, elt) {
+ for (var i = 0; i < array.length; ++i)
+ if (array[i] == elt) return i;
return -1;
}
+ if ([].indexOf) indexOf = function(array, elt) { return array.indexOf(elt); };
+ function map(array, f) {
+ var out = [];
+ for (var i = 0; i < array.length; i++) out[i] = f(array[i], i);
+ return out;
+ }
+ if ([].map) map = function(array, f) { return array.map(f); };
function createObj(base, props) {
- function Obj() {}
- Obj.prototype = base;
- var inst = new Obj();
+ var inst;
+ if (Object.create) {
+ inst = Object.create(base);
+ } else {
+ var ctor = function() {};
+ ctor.prototype = base;
+ inst = new ctor();
+ }
if (props) copyObj(props, inst);
return inst;
- }
+ };
function copyObj(obj, target) {
if (!target) target = {};
@@ -5600,27 +6875,27 @@
return target;
}
- function emptyArray(size) {
- for (var a = [], i = 0; i < size; ++i) a.push(undefined);
- return a;
- }
-
function bind(f) {
var args = Array.prototype.slice.call(arguments, 1);
return function(){return f.apply(null, args);};
}
- var nonASCIISingleCaseWordChar = /[\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/;
- function isWordChar(ch) {
+ var nonASCIISingleCaseWordChar = /[\u00df\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/;
+ var isWordChar = CodeMirror.isWordChar = function(ch) {
return /\w/.test(ch) || ch > "\x80" &&
(ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch));
- }
+ };
function isEmpty(obj) {
for (var n in obj) if (obj.hasOwnProperty(n) && obj[n]) return false;
return true;
}
+ // Extending unicode characters. A series of a non-extending char +
+ // any number of extending chars is treated as a single unit as far
+ // as editing and measuring is concerned. This is not fully correct,
+ // since some scripts/fonts/browsers also treat other configurations
+ // of code points as a group.
var extendingChars = /[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0cc6\u0ccc\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0d3e\u0d41-\u0d44\u0d4d\u0d57\u0d62\u0d63\u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f80-\u0f84\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085\u1086\u108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b7-\u17bd\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193b\u1a17\u1a18\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-\u1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8\u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u200c\u200d\u20d0-\u20f0\u2cef-\u2cf1\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua66f-\ua672\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\ua80b\ua825\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ua9b6-\ua9b9\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\udc00-\udfff\ufb1e\ufe00-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/;
function isExtendingChar(ch) { return ch.charCodeAt(0) >= 768 && extendingChars.test(ch); }
@@ -5630,11 +6905,27 @@
var e = document.createElement(tag);
if (className) e.className = className;
if (style) e.style.cssText = style;
- if (typeof content == "string") setTextContent(e, content);
+ if (typeof content == "string") e.appendChild(document.createTextNode(content));
else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]);
return e;
}
+ var range;
+ if (document.createRange) range = function(node, start, end) {
+ var r = document.createRange();
+ r.setEnd(node, end);
+ r.setStart(node, start);
+ return r;
+ };
+ else range = function(node, start, end) {
+ var r = document.body.createTextRange();
+ r.moveToElementText(node.parentNode);
+ r.collapse(true);
+ r.moveEnd("character", end);
+ r.moveStart("character", start);
+ return r;
+ };
+
function removeChildren(e) {
for (var count = e.childNodes.length; count > 0; --count)
e.removeChild(e.firstChild);
@@ -5645,17 +6936,20 @@
return removeChildren(parent).appendChild(e);
}
- function setTextContent(e, str) {
- if (ie_lt9) {
- e.innerHTML = "";
- e.appendChild(document.createTextNode(str));
- } else e.textContent = str;
+ function contains(parent, child) {
+ if (parent.contains)
+ return parent.contains(child);
+ while (child = child.parentNode)
+ if (child == parent) return true;
}
- function getRect(node) {
- return node.getBoundingClientRect();
- }
- CodeMirror.replaceGetRect = function(f) { getRect = f; };
+ function activeElt() { return document.activeElement; }
+ // Older versions of IE throws unspecified error when touching
+ // document.activeElement in some cases (during loading, in iframe)
+ if (ie_upto10) activeElt = function() {
+ try { return document.activeElement; }
+ catch(e) { return document.body; }
+ };
// FEATURE DETECTION
@@ -5663,41 +6957,11 @@
var dragAndDrop = function() {
// There is *some* kind of drag-and-drop support in IE6-8, but I
// couldn't get it to work yet.
- if (ie_lt9) return false;
+ if (ie_upto8) return false;
var div = elt('div');
return "draggable" in div || "dragDrop" in div;
}();
- // For a reason I have yet to figure out, some browsers disallow
- // word wrapping between certain characters *only* if a new inline
- // element is started between them. This makes it hard to reliably
- // measure the position of things, since that requires inserting an
- // extra span. This terribly fragile set of tests matches the
- // character combinations that suffer from this phenomenon on the
- // various browsers.
- function spanAffectsWrapping() { return false; }
- if (gecko) // Only for "$'"
- spanAffectsWrapping = function(str, i) {
- return str.charCodeAt(i - 1) == 36 && str.charCodeAt(i) == 39;
- };
- else if (safari && !/Version\/([6-9]|\d\d)\b/.test(navigator.userAgent))
- spanAffectsWrapping = function(str, i) {
- return /\-[^ \-?]|\?[^ !\'\"\),.\-\/:;\?\]\}]/.test(str.slice(i - 1, i + 1));
- };
- else if (webkit && /Chrome\/(?:29|[3-9]\d|\d\d\d)\./.test(navigator.userAgent))
- spanAffectsWrapping = function(str, i) {
- var code = str.charCodeAt(i - 1);
- return code >= 8208 && code <= 8212;
- };
- else if (webkit)
- spanAffectsWrapping = function(str, i) {
- if (i > 1 && str.charCodeAt(i - 1) == 45) {
- if (/\w/.test(str.charAt(i - 2)) && /[^\-?\.]/.test(str.charAt(i))) return true;
- if (i > 2 && /[\d\.,]/.test(str.charAt(i - 2)) && /[\d\.,]/.test(str.charAt(i))) return false;
- }
- return /[~!#%&*)=+}\]\\|\"\.>,:;][({[<]|-[^\-?\.\u2010-\u201f\u2026]|\?[\w~`@#$%\^&*(_=+{[|><]|\u2026[\w~`@#$%\^&*(_=+{[><]/.test(str.slice(i - 1, i + 1));
- };
-
var knownScrollbarWidth;
function scrollbarWidth(measure) {
if (knownScrollbarWidth != null) return knownScrollbarWidth;
@@ -5714,15 +6978,26 @@
var test = elt("span", "\u200b");
removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("x")]));
if (measure.firstChild.offsetHeight != 0)
- zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !ie_lt8;
+ zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !ie_upto7;
}
if (zwspSupported) return elt("span", "\u200b");
else return elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px");
}
+ // Feature-detect IE's crummy client rect reporting for bidi text
+ var badBidiRects;
+ function hasBadBidiRects(measure) {
+ if (badBidiRects != null) return badBidiRects;
+ var txt = removeChildrenAndAdd(measure, document.createTextNode("A\u062eA"));
+ var r0 = range(txt, 0, 1).getBoundingClientRect();
+ if (r0.left == r0.right) return false;
+ var r1 = range(txt, 1, 2).getBoundingClientRect();
+ return badBidiRects = (r1.right - r0.right < 3);
+ }
+
// See if "".split is the broken IE version, if so, provide an
// alternative way to split lines.
- var splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) {
+ var splitLines = CodeMirror.splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) {
var pos = 0, result = [], l = string.length;
while (pos <= l) {
var nl = string.indexOf("\n", pos);
@@ -5739,7 +7014,6 @@
}
return result;
} : function(string){return string.split(/\r\n?|\n/);};
- CodeMirror.splitLines = splitLines;
var hasSelection = window.getSelection ? function(te) {
try { return te.selectionStart != te.selectionEnd; }
@@ -5755,10 +7029,10 @@
var e = elt("div");
if ("oncopy" in e) return true;
e.setAttribute("oncopy", "return;");
- return typeof e.oncopy == 'function';
+ return typeof e.oncopy == "function";
})();
- // KEY NAMING
+ // KEY NAMES
var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt",
19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End",
@@ -5804,19 +7078,21 @@
function lineStart(cm, lineN) {
var line = getLine(cm.doc, lineN);
- var visual = visualLine(cm.doc, line);
+ var visual = visualLine(line);
if (visual != line) lineN = lineNo(visual);
var order = getOrder(visual);
var ch = !order ? 0 : order[0].level % 2 ? lineRight(visual) : lineLeft(visual);
return Pos(lineN, ch);
}
function lineEnd(cm, lineN) {
- var merged, line;
- while (merged = collapsedSpanAtEnd(line = getLine(cm.doc, lineN)))
- lineN = merged.find().to.line;
+ var merged, line = getLine(cm.doc, lineN);
+ while (merged = collapsedSpanAtEnd(line)) {
+ line = merged.find(1, true).line;
+ lineN = null;
+ }
var order = getOrder(line);
var ch = !order ? line.text.length : order[0].level % 2 ? lineLeft(line) : lineRight(line);
- return Pos(lineN, ch);
+ return Pos(lineN == null ? lineNo(line) : lineN, ch);
}
function compareBidiLevel(order, a, b) {
@@ -5853,12 +7129,11 @@
return pos;
}
- // This is somewhat involved. It is needed in order to move
- // 'visually' through bi-directional text -- i.e., pressing left
- // should make the cursor go left, even when in RTL text. The
- // tricky part is the 'jumps', where RTL and LTR text touch each
- // other. This often requires the cursor offset to move more than
- // one unit, in order to visually move one unit.
+ // This is needed in order to move 'visually' through bi-directional
+ // text -- i.e., pressing left should make the cursor go left, even
+ // when in RTL text. The tricky part is the 'jumps', where RTL and
+ // LTR text touch each other. This often requires the cursor offset
+ // to move more than one unit, in order to visually move one unit.
function moveVisually(line, start, dir, byUnit) {
var bidi = getOrder(line);
if (!bidi) return moveLogically(line, start, dir, byUnit);
@@ -5913,14 +7188,16 @@
// objects) in the order in which they occur visually.
var bidiOrdering = (function() {
// Character types for codepoints 0 to 0xff
- var lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLL";
+ var lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN";
// Character types for codepoints 0x600 to 0x6ff
- var arabicTypes = "rrrrrrrrrrrr,rNNmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmrrrrrrrnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmNmmmmrrrrrrrrrrrrrrrrrr";
+ var arabicTypes = "rrrrrrrrrrrr,rNNmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmrrrrrrrnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmNmmmm";
function charType(code) {
- if (code <= 0xff) return lowTypes.charAt(code);
+ if (code <= 0xf7) return lowTypes.charAt(code);
else if (0x590 <= code && code <= 0x5f4) return "R";
- else if (0x600 <= code && code <= 0x6ff) return arabicTypes.charAt(code - 0x600);
- else if (0x700 <= code && code <= 0x8ac) return "r";
+ else if (0x600 <= code && code <= 0x6ed) return arabicTypes.charAt(code - 0x600);
+ else if (0x6ee <= code && code <= 0x8ac) return "r";
+ else if (0x2000 <= code && code <= 0x200b) return "w";
+ else if (code == 0x200c) return "b";
else return "L";
}
@@ -5929,6 +7206,11 @@
// Browsers seem to always treat the boundaries of block elements as being L.
var outerType = "L";
+ function BidiSpan(level, from, to) {
+ this.level = level;
+ this.from = from; this.to = to;
+ }
+
return function(str) {
if (!bidiRE.test(str)) return false;
var len = str.length, types = [];
@@ -6018,32 +7300,32 @@
if (countsAsLeft.test(types[i])) {
var start = i;
for (++i; i < len && countsAsLeft.test(types[i]); ++i) {}
- order.push({from: start, to: i, level: 0});
+ order.push(new BidiSpan(0, start, i));
} else {
var pos = i, at = order.length;
for (++i; i < len && types[i] != "L"; ++i) {}
for (var j = pos; j < i;) {
if (countsAsNum.test(types[j])) {
- if (pos < j) order.splice(at, 0, {from: pos, to: j, level: 1});
+ if (pos < j) order.splice(at, 0, new BidiSpan(1, pos, j));
var nstart = j;
for (++j; j < i && countsAsNum.test(types[j]); ++j) {}
- order.splice(at, 0, {from: nstart, to: j, level: 2});
+ order.splice(at, 0, new BidiSpan(2, nstart, j));
pos = j;
} else ++j;
}
- if (pos < i) order.splice(at, 0, {from: pos, to: i, level: 1});
+ if (pos < i) order.splice(at, 0, new BidiSpan(1, pos, i));
}
}
if (order[0].level == 1 && (m = str.match(/^\s+/))) {
order[0].from = m[0].length;
- order.unshift({from: 0, to: m[0].length, level: 0});
+ order.unshift(new BidiSpan(0, 0, m[0].length));
}
if (lst(order).level == 1 && (m = str.match(/\s+$/))) {
lst(order).to -= m[0].length;
- order.push({from: len - m[0].length, to: len, level: 0});
+ order.push(new BidiSpan(0, len - m[0].length, len));
}
if (order[0].level != lst(order).level)
- order.push({from: len, to: len, level: order[0].level});
+ order.push(new BidiSpan(order[0].level, len, len));
return order;
};
@@ -6051,7 +7333,7 @@
// THE END
- CodeMirror.version = "3.21.1";
+ CodeMirror.version = "4.0.3";
return CodeMirror;
-})();
+});
diff --git a/Source/devtools/front_end/cm/comment.js b/Source/devtools/front_end/cm/comment.js
index cd2123e..1eb9a05 100644
--- a/Source/devtools/front_end/cm/comment.js
+++ b/Source/devtools/front_end/cm/comment.js
@@ -1,4 +1,11 @@
-(function() {
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ mod(require("../../lib/codemirror"));
+ else if (typeof define == "function" && define.amd) // AMD
+ define(["../../lib/codemirror"], mod);
+ else // Plain browser env
+ mod(CodeMirror);
+})(function(CodeMirror) {
"use strict";
var noOptions = {};
@@ -11,8 +18,21 @@
}
CodeMirror.commands.toggleComment = function(cm) {
- var from = cm.getCursor("start"), to = cm.getCursor("end");
- cm.uncomment(from, to) || cm.lineComment(from, to);
+ var minLine = Infinity, ranges = cm.listSelections(), mode = null;
+ for (var i = ranges.length - 1; i >= 0; i--) {
+ var from = ranges[i].from(), to = ranges[i].to();
+ if (from.line >= minLine) continue;
+ if (to.line >= minLine) to = Pos(minLine, 0);
+ minLine = from.line;
+ if (mode == null) {
+ if (cm.uncomment(from, to)) mode = "un";
+ else { cm.lineComment(from, to); mode = "line"; }
+ } else if (mode == "un") {
+ cm.uncomment(from, to);
+ } else {
+ cm.lineComment(from, to);
+ }
+ }
};
CodeMirror.defineExtension("lineComment", function(from, to, options) {
@@ -96,8 +116,9 @@
for (var i = start; i <= end; ++i) {
var line = self.getLine(i);
var found = line.indexOf(lineString);
+ if (found > -1 && !/comment/.test(self.getTokenTypeAt(Pos(i, found + 1)))) found = -1;
if (found == -1 && (i != end || i == start) && nonWS.test(line)) break lineComment;
- if (i != start && found > -1 && nonWS.test(line.slice(0, found))) break lineComment;
+ if (found > -1 && nonWS.test(line.slice(0, found))) break lineComment;
lines.push(line);
}
self.operation(function() {
@@ -124,7 +145,10 @@
endLine = self.getLine(--end);
close = endLine.lastIndexOf(endString);
}
- if (open == -1 || close == -1) return false;
+ if (open == -1 || close == -1 ||
+ !/comment/.test(self.getTokenTypeAt(Pos(start, open + 1))) ||
+ !/comment/.test(self.getTokenTypeAt(Pos(end, close + 1))))
+ return false;
self.operation(function() {
self.replaceRange("", Pos(end, close - (pad && endLine.slice(close - pad.length, close) == pad ? pad.length : 0)),
@@ -142,4 +166,4 @@
});
return true;
});
-})();
+});
diff --git a/Source/devtools/front_end/cm/headlesscodemirror.js b/Source/devtools/front_end/cm/headlesscodemirror.js
index 3433e8d..67c7866 100644
--- a/Source/devtools/front_end/cm/headlesscodemirror.js
+++ b/Source/devtools/front_end/cm/headlesscodemirror.js
@@ -1,8 +1,6 @@
// Content of the function is equal to runmode-standalone.js file
// from CodeMirror distribution
(function(window) {
-/* Just enough of CodeMirror to run runMode under node.js */
-
window.CodeMirror = {};
(function() {
@@ -152,4 +150,5 @@
}
};
})();
+
}(this))
diff --git a/Source/devtools/front_end/cm/markselection.js b/Source/devtools/front_end/cm/markselection.js
index c97776e..ae0d393 100644
--- a/Source/devtools/front_end/cm/markselection.js
+++ b/Source/devtools/front_end/cm/markselection.js
@@ -4,7 +4,14 @@
// selected text the CSS class given as option value, or
// "CodeMirror-selectedtext" when the value is not a string.
-(function() {
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ mod(require("../../lib/codemirror"));
+ else if (typeof define == "function" && define.amd) // AMD
+ define(["../../lib/codemirror"], mod);
+ else // Plain browser env
+ mod(CodeMirror);
+})(function(CodeMirror) {
"use strict";
CodeMirror.defineOption("styleSelectedText", false, function(cm, val, old) {
@@ -34,10 +41,7 @@
var CHUNK_SIZE = 8;
var Pos = CodeMirror.Pos;
-
- function cmp(pos1, pos2) {
- return pos1.line - pos2.line || pos1.ch - pos2.ch;
- }
+ var cmp = CodeMirror.cmpPos;
function coverRange(cm, from, to, addAt) {
if (cmp(from, to) == 0) return;
@@ -63,13 +67,16 @@
function reset(cm) {
clear(cm);
- var from = cm.getCursor("start"), to = cm.getCursor("end");
- coverRange(cm, from, to);
+ var ranges = cm.listSelections();
+ for (var i = 0; i < ranges.length; i++)
+ coverRange(cm, ranges[i].from(), ranges[i].to());
}
function update(cm) {
+ if (!cm.somethingSelected()) return clear(cm);
+ if (cm.listSelections().length > 1) return reset(cm);
+
var from = cm.getCursor("start"), to = cm.getCursor("end");
- if (cmp(from, to) == 0) return clear(cm);
var array = cm.state.markedSelection;
if (!array.length) return coverRange(cm, from, to);
@@ -105,4 +112,4 @@
}
}
}
-})();
+});
diff --git a/Source/devtools/front_end/cm/matchbrackets.js b/Source/devtools/front_end/cm/matchbrackets.js
index 9d9b388..b889fa3 100644
--- a/Source/devtools/front_end/cm/matchbrackets.js
+++ b/Source/devtools/front_end/cm/matchbrackets.js
@@ -1,73 +1,96 @@
-(function() {
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ mod(require("../../lib/codemirror"));
+ else if (typeof define == "function" && define.amd) // AMD
+ define(["../../lib/codemirror"], mod);
+ else // Plain browser env
+ mod(CodeMirror);
+})(function(CodeMirror) {
var ie_lt8 = /MSIE \d/.test(navigator.userAgent) &&
(document.documentMode == null || document.documentMode < 8);
var Pos = CodeMirror.Pos;
var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"};
- function findMatchingBracket(cm, where, strict) {
- var state = cm.state.matchBrackets;
- var maxScanLen = (state && state.maxScanLineLength) || 10000;
- var maxScanLines = (state && state.maxScanLines) || 100;
- var cur = where || cm.getCursor(), line = cm.getLineHandle(cur.line), pos = cur.ch - 1;
+ function findMatchingBracket(cm, where, strict, config) {
+ var line = cm.getLineHandle(where.line), pos = where.ch - 1;
var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)];
if (!match) return null;
- var forward = match.charAt(1) == ">", d = forward ? 1 : -1;
- if (strict && forward != (pos == cur.ch)) return null;
- var style = cm.getTokenTypeAt(Pos(cur.line, pos + 1));
+ var dir = match.charAt(1) == ">" ? 1 : -1;
+ if (strict && (dir > 0) != (pos == where.ch)) return null;
+ var style = cm.getTokenTypeAt(Pos(where.line, pos + 1));
- var stack = [line.text.charAt(pos)], re = /[(){}[\]]/;
- function scan(line, lineNo, start) {
- if (!line.text) return;
- var pos = forward ? 0 : line.text.length - 1, end = forward ? line.text.length : -1;
- if (line.text.length > maxScanLen) return null;
- if (start != null) pos = start + d;
- for (; pos != end; pos += d) {
- var ch = line.text.charAt(pos);
- if (re.test(ch) && cm.getTokenTypeAt(Pos(lineNo, pos + 1)) == style) {
+ var found = scanForBracket(cm, Pos(where.line, pos + (dir > 0 ? 1 : 0)), dir, style || null, config);
+ return {from: Pos(where.line, pos), to: found && found.pos,
+ match: found && found.ch == match.charAt(0), forward: dir > 0};
+ }
+
+ // bracketRegex is used to specify which type of bracket to scan
+ // should be a regexp, e.g. /[[\]]/
+ //
+ // Note: If "where" is on an open bracket, then this bracket is ignored.
+ function scanForBracket(cm, where, dir, style, config) {
+ var maxScanLen = (config && config.maxScanLineLength) || 10000;
+ var maxScanLines = (config && config.maxScanLines) || 500;
+
+ var stack = [];
+ var re = config && config.bracketRegex ? config.bracketRegex : /[(){}[\]]/;
+ var lineEnd = dir > 0 ? Math.min(where.line + maxScanLines, cm.lastLine() + 1)
+ : Math.max(cm.firstLine() - 1, where.line - maxScanLines);
+ for (var lineNo = where.line; lineNo != lineEnd; lineNo += dir) {
+ var line = cm.getLine(lineNo);
+ if (!line) continue;
+ var pos = dir > 0 ? 0 : line.length - 1, end = dir > 0 ? line.length : -1;
+ if (line.length > maxScanLen) continue;
+ if (lineNo == where.line) pos = where.ch - (dir < 0 ? 1 : 0);
+ for (; pos != end; pos += dir) {
+ var ch = line.charAt(pos);
+ if (re.test(ch) && (style === undefined || cm.getTokenTypeAt(Pos(lineNo, pos + 1)) == style)) {
var match = matching[ch];
- if (match.charAt(1) == ">" == forward) stack.push(ch);
- else if (stack.pop() != match.charAt(0)) return {pos: pos, match: false};
- else if (!stack.length) return {pos: pos, match: true};
+ if ((match.charAt(1) == ">") == (dir > 0)) stack.push(ch);
+ else if (!stack.length) return {pos: Pos(lineNo, pos), ch: ch};
+ else stack.pop();
}
}
}
- for (var i = cur.line, found, e = forward ? Math.min(i + maxScanLines, cm.lineCount()) : Math.max(-1, i - maxScanLines); i != e; i+=d) {
- if (i == cur.line) found = scan(line, i, pos);
- else found = scan(cm.getLineHandle(i), i);
- if (found) break;
- }
- return {from: Pos(cur.line, pos), to: found && Pos(i, found.pos),
- match: found && found.match, forward: forward};
}
- function matchBrackets(cm, autoclear) {
+ function matchBrackets(cm, autoclear, config) {
// Disable brace matching in long lines, since it'll cause hugely slow updates
var maxHighlightLen = cm.state.matchBrackets.maxHighlightLineLength || 1000;
- var found = findMatchingBracket(cm);
- if (!found || cm.getLine(found.from.line).length > maxHighlightLen ||
- found.to && cm.getLine(found.to.line).length > maxHighlightLen)
- return;
+ var marks = [], ranges = cm.listSelections();
+ for (var i = 0; i < ranges.length; i++) {
+ var match = ranges[i].empty() && findMatchingBracket(cm, ranges[i].head, false, config);
+ if (match && cm.getLine(match.from.line).length <= maxHighlightLen &&
+ match.to && cm.getLine(match.to.line).length <= maxHighlightLen) {
+ var style = match.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket";
+ marks.push(cm.markText(match.from, Pos(match.from.line, match.from.ch + 1), {className: style}));
+ if (match.to)
+ marks.push(cm.markText(match.to, Pos(match.to.line, match.to.ch + 1), {className: style}));
+ }
+ }
- var style = found.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket";
- var one = cm.markText(found.from, Pos(found.from.line, found.from.ch + 1), {className: style});
- var two = found.to && cm.markText(found.to, Pos(found.to.line, found.to.ch + 1), {className: style});
- // Kludge to work around the IE bug from issue #1193, where text
- // input stops going to the textare whever this fires.
- if (ie_lt8 && cm.state.focused) cm.display.input.focus();
- var clear = function() {
- cm.operation(function() { one.clear(); two && two.clear(); });
- };
- if (autoclear) setTimeout(clear, 800);
- else return clear;
+ if (marks.length) {
+ // Kludge to work around the IE bug from issue #1193, where text
+ // input stops going to the textare whever this fires.
+ if (ie_lt8 && cm.state.focused) cm.display.input.focus();
+
+ var clear = function() {
+ cm.operation(function() {
+ for (var i = 0; i < marks.length; i++) marks[i].clear();
+ });
+ };
+ if (autoclear) setTimeout(clear, 800);
+ else return clear;
+ }
}
var currentlyHighlighted = null;
function doMatchBrackets(cm) {
cm.operation(function() {
if (currentlyHighlighted) {currentlyHighlighted(); currentlyHighlighted = null;}
- if (!cm.somethingSelected()) currentlyHighlighted = matchBrackets(cm, false);
+ currentlyHighlighted = matchBrackets(cm, false, cm.state.matchBrackets);
});
}
@@ -81,7 +104,10 @@
});
CodeMirror.defineExtension("matchBrackets", function() {matchBrackets(this, true);});
- CodeMirror.defineExtension("findMatchingBracket", function(pos, strict){
- return findMatchingBracket(this, pos, strict);
+ CodeMirror.defineExtension("findMatchingBracket", function(pos, strict, config){
+ return findMatchingBracket(this, pos, strict, config);
});
-})();
+ CodeMirror.defineExtension("scanForBracket", function(pos, dir, style, config){
+ return scanForBracket(this, pos, dir, style, config);
+ });
+});
diff --git a/Source/devtools/front_end/externs.js b/Source/devtools/front_end/externs.js
index cdc8e0a..98137c1 100644
--- a/Source/devtools/front_end/externs.js
+++ b/Source/devtools/front_end/externs.js
@@ -350,6 +350,7 @@
getScrollInfo: function() { },
getScrollerElement: function() { },
getSelection: function() { },
+ getSelections: function() { },
getStateAfter: function(line) { },
getTokenAt: function(pos) { },
getValue: function(lineSep) { },
@@ -371,6 +372,7 @@
*/
lineAtHeight: function(height, mode) { },
linkedDoc: function(options) { },
+ listSelections: function() { },
markClean: function() { },
markText: function(from, to, options) { },
moveH: function(dir, unit) { },
@@ -398,6 +400,7 @@
setLine: function(line, text) { },
setOption: function(option, value) { },
setSelection: function(anchor, head) { },
+ setSelections: function(selections) { },
setSize: function(width, height) { },
setValue: function(code) { },
somethingSelected: function() { },
@@ -405,8 +408,8 @@
undo: function() { },
unlinkDoc: function(other) { }
}
-/** @type {number} */
-CodeMirror.prototype.lineCount;
+/** @type {!{cursorDiv: Element}} */
+CodeMirror.prototype.display;
CodeMirror.Pass;
CodeMirror.showHint = function(codeMirror, hintintFunction) { };
CodeMirror.commands = {};
diff --git a/Source/devtools/front_end/heapProfiler.css b/Source/devtools/front_end/heapProfiler.css
index 6e1fb56..8ee6d63 100644
--- a/Source/devtools/front_end/heapProfiler.css
+++ b/Source/devtools/front_end/heapProfiler.css
@@ -64,43 +64,7 @@
color: inherit;
}
-.heap-snapshot-view .data-grid td.count-column {
- text-align: right;
-}
-
-.heap-snapshot-view .data-grid td.addedCount-column {
- text-align: right;
-}
-
-.heap-snapshot-view .data-grid td.removedCount-column {
- text-align: right;
-}
-
-.heap-snapshot-view .data-grid td.countDelta-column {
- text-align: right;
-}
-
-.heap-snapshot-view .data-grid td.addedSize-column {
- text-align: right;
-}
-
-.heap-snapshot-view .data-grid td.removedSize-column {
- text-align: right;
-}
-
-.heap-snapshot-view .data-grid td.sizeDelta-column {
- text-align: right;
-}
-
-.heap-snapshot-view .data-grid td.shallowSize-column {
- text-align: right;
-}
-
-.heap-snapshot-view .data-grid td.retainedSize-column {
- text-align: right;
-}
-
-.heap-snapshot-view .data-grid td.distanceToWindow-column {
+.heap-snapshot-view .data-grid td.numeric-column {
text-align: right;
}
@@ -150,7 +114,7 @@
overflow: hidden;
}
-.heap-snapshot-view .retainers-view-header {
+.heap-snapshot-view .heap-snapshot-view-resizer {
background-image: url(Images/statusbarResizerVertical.png);
background-color: rgb(236, 236, 236);
border-bottom: 1px solid rgb(179, 179, 179);
@@ -159,7 +123,7 @@
flex: 0 0 21px;
}
-.heap-snapshot-view .retainers-view-header .title > span {
+.heap-snapshot-view .heap-snapshot-view-resizer .title > span {
display: inline-block;
padding-top: 3px;
vertical-align: middle;
@@ -167,7 +131,7 @@
margin-right: 8px;
}
-.heap-snapshot-view .retainers-view-header * {
+.heap-snapshot-view .heap-snapshot-view-resizer * {
pointer-events: none;
}
diff --git a/Source/devtools/front_end/helpScreen.css b/Source/devtools/front_end/helpScreen.css
index a6fd786..efdc0d9 100644
--- a/Source/devtools/front_end/helpScreen.css
+++ b/Source/devtools/front_end/helpScreen.css
@@ -132,6 +132,7 @@
.settings-tab .field-error-message {
color: DarkRed;
+ height: 0px; // Avoid changing element height when content is set.
}
.help-line {
@@ -197,7 +198,7 @@
.settings-tab label {
padding-right: 4px;
- display: flex;
+ display: inline-flex;
}
#general-tab-content .help-block fieldset legend {
diff --git a/Source/devtools/front_end/inspector.html b/Source/devtools/front_end/inspector.html
index e0f342a..42eef7a 100644
--- a/Source/devtools/front_end/inspector.html
+++ b/Source/devtools/front_end/inspector.html
@@ -42,6 +42,7 @@
<script type="text/javascript" src="DOMExtension.js"></script>
<script type="text/javascript" src="treeoutline.js"></script>
<script type="text/javascript" src="WebInspector.js"></script>
+ <script type="text/javascript" src="SettingsUI.js"></script>
<script type="text/javascript" src="Main.js"></script>
<script type="text/javascript" src="ModuleManager.js"></script>
<script type="text/javascript" src="Platform.js"></script>
@@ -54,7 +55,6 @@
<script type="text/javascript" src="Target.js"></script>
<script type="text/javascript" src="NotificationService.js"></script>
<script type="text/javascript" src="Settings.js"></script>
- <script type="text/javascript" src="SettingsUI.js"></script>
<script type="text/javascript" src="View.js"></script>
<script type="text/javascript" src="UIUtils.js"></script>
<script type="text/javascript" src="HelpScreen.js"></script>
@@ -185,6 +185,7 @@
<script type="text/javascript" src="CPUProfilerModel.js"></script>
<script type="text/javascript" src="DockController.js"></script>
<script type="text/javascript" src="TracingAgent.js"></script>
+ <script type="text/javascript" src="TracingModel.js"></script>
<script type="text/javascript" src="ScreencastView.js"></script>
<script type="text/javascript" src="Tests.js"></script>
<script type="text/javascript" src="FlameChart.js"></script>
diff --git a/Source/devtools/front_end/modules.js b/Source/devtools/front_end/modules.js
index 69bdf10..9af7506 100644
--- a/Source/devtools/front_end/modules.js
+++ b/Source/devtools/front_end/modules.js
@@ -78,6 +78,31 @@
}
],
className: "WebInspector.Main.DebugReloadActionDelegate"
+ },
+ {
+ type: "ui-setting",
+ title: "Disable cache (while DevTools is open)",
+ settingName: "cacheDisabled",
+ settingType: "checkbox"
+ },
+ {
+ type: "ui-setting",
+ section: "Appearance",
+ title: "Split panels vertically when docked to right",
+ settingName: "splitVerticallyWhenDockedToRight",
+ settingType: "checkbox"
+ },
+ {
+ type: "ui-setting",
+ section: "Appearance",
+ settingType: "custom",
+ className: "WebInspector.Main.ShortcutPanelSwitchSettingDelegate"
+ },
+ {
+ type: "ui-setting",
+ section: "Extensions",
+ settingType: "custom",
+ className: "WebInspector.HandlerRegistry.OpenAnchorLocationSettingDelegate"
}
]
},
@@ -119,6 +144,47 @@
type: "@WebInspector.Revealer",
contextTypes: ["WebInspector.DOMNode"],
className: "WebInspector.ElementsPanel.DOMNodeRevealer"
+ },
+ {
+ type: "ui-setting",
+ section: "Elements",
+ title: "Color format",
+ settingName: "colorFormat",
+ settingType: "select",
+ options: [
+ [ "As authored", "original" ],
+ [ "HEX: #DAC0DE", "hex", true ],
+ [ "RGB: rgb(128, 255, 255)", "rgb", true ],
+ [ "HSL: hsl(300, 80%, 90%)", "hsl", true ]
+ ]
+ },
+ {
+ type: "ui-setting",
+ section: "Elements",
+ title: "Show user agent styles",
+ settingName: "showUserAgentStyles",
+ settingType: "checkbox"
+ },
+ {
+ type: "ui-setting",
+ section: "Elements",
+ title: "Show user agent shadow DOM",
+ settingName: "showUAShadowDOM",
+ settingType: "checkbox"
+ },
+ {
+ type: "ui-setting",
+ section: "Elements",
+ title: "Word wrap",
+ settingName: "domWordWrap",
+ settingType: "checkbox"
+ },
+ {
+ type: "ui-setting",
+ section: "Elements",
+ title: "Show rulers",
+ settingName: "showMetricsRulers",
+ settingType: "checkbox"
}
],
scripts: [ "ElementsPanel.js" ]
@@ -157,6 +223,19 @@
type: "@WebInspector.TokenizerFactory",
className: "WebInspector.CodeMirrorUtils.TokenizerFactory"
},
+ {
+ type: "ui-setting",
+ section: "Sources",
+ title: "Default indentation",
+ settingName: "textEditorIndent",
+ settingType: "select",
+ options: [
+ ["2 spaces", " "],
+ ["4 spaces", " "],
+ ["8 spaces", " "],
+ ["Tab character", "\t"]
+ ]
+ }
],
scripts: [ "CodeMirrorTextEditor.js" ]
},
@@ -237,6 +316,83 @@
}
],
className: "WebInspector.SourcesPanel.ShowGoToSourceDialogActionDelegate"
+ },
+ {
+ type: "ui-setting",
+ settingName: "javaScriptDisabled",
+ settingType: "custom",
+ className: "WebInspector.SourcesPanel.DisableJavaScriptSettingDelegate"
+ },
+ {
+ type: "ui-setting",
+ section: "Sources",
+ title: "Search in content scripts",
+ settingName: "searchInContentScripts",
+ settingType: "checkbox"
+ },
+ {
+ type: "ui-setting",
+ section: "Sources",
+ title: "Enable JavaScript source maps",
+ settingName: "jsSourceMapsEnabled",
+ settingType: "checkbox"
+ },
+ {
+ type: "ui-setting",
+ section: "Sources",
+ title: "Detect indentation",
+ settingName: "textEditorAutoDetectIndent",
+ settingType: "checkbox"
+ },
+ {
+ type: "ui-setting",
+ section: "Sources",
+ title: "Autocompletion",
+ settingName: "textEditorAutocompletion",
+ settingType: "checkbox"
+ },
+ {
+ type: "ui-setting",
+ section: "Sources",
+ title: "Bracket matching",
+ settingName: "textEditorBracketMatching",
+ settingType: "checkbox"
+ },
+ {
+ type: "ui-setting",
+ section: "Sources",
+ title: "Show whitespace characters",
+ settingName: "showWhitespacesInEditor",
+ settingType: "checkbox"
+ },
+ {
+ type: "ui-setting",
+ section: "Sources",
+ title: "Enable CSS source maps",
+ settingName: "cssSourceMapsEnabled",
+ settingType: "checkbox"
+ },
+ {
+ type: "ui-setting",
+ title: "Auto-reload generated CSS",
+ parentSettingName: "cssSourceMapsEnabled",
+ settingName: "cssReloadEnabled",
+ settingType: "checkbox"
+ },
+ {
+ type: "ui-setting",
+ section: "Sources",
+ experiment: "frameworksDebuggingSupport",
+ title: "Skip stepping through sources with particular names",
+ settingName: "skipStackFramesSwitch",
+ settingType: "checkbox"
+ },
+ {
+ type: "ui-setting",
+ experiment: "frameworksDebuggingSupport",
+ parentSettingName: "skipStackFramesSwitch",
+ settingType: "custom",
+ className: "WebInspector.SourcesPanel.SkipStackFramePatternSettingDelegate"
}
],
scripts: [ "SourcesPanel.js" ]
@@ -268,6 +424,20 @@
type: "@WebInspector.ContextMenu.Provider",
contextTypes: ["WebInspector.RemoteObject"],
className: "WebInspector.ProfilesPanel.ContextMenuProvider"
+ },
+ {
+ type: "ui-setting",
+ section: "Profiler",
+ title: "Show advanced heap snapshot properties",
+ settingName: "showAdvancedHeapSnapshotProperties",
+ settingType: "checkbox"
+ },
+ {
+ type: "ui-setting",
+ section: "Profiler",
+ title: "High resolution CPU profiling",
+ settingName: "highResolutionCpuProfiling",
+ settingType: "checkbox"
}
],
scripts: [ "ProfilesPanel.js" ]
@@ -333,6 +503,27 @@
}
],
className: "WebInspector.ConsoleView.ShowConsoleActionDelegate"
+ },
+ {
+ type: "ui-setting",
+ section: "Console",
+ title: "Log XMLHttpRequests",
+ settingName: "monitoringXHREnabled",
+ settingType: "checkbox"
+ },
+ {
+ type: "ui-setting",
+ section: "Console",
+ title: "Preserve log upon navigation",
+ settingName: "preserveConsoleLog",
+ settingType: "checkbox"
+ },
+ {
+ type: "ui-setting",
+ section: "Console",
+ title: "Show timestamps",
+ settingName: "consoleTimestampsEnabled",
+ settingType: "checkbox"
}
],
scripts: [ "ConsolePanel.js" ]
diff --git a/Source/devtools/front_end/timelinePanel.css b/Source/devtools/front_end/timelinePanel.css
index be18c99..3462fa2 100644
--- a/Source/devtools/front_end/timelinePanel.css
+++ b/Source/devtools/front_end/timelinePanel.css
@@ -764,6 +764,14 @@
padding: 10px;
}
+.timeline-details-view-row-details table {
+ padding-left: 10px;
+}
+
+.timeline-details-view-row-details table td {
+ text-align: left;
+}
+
.timeline-details-view-row-stack-trace {
padding: 4px 0 4px 20px;
}
diff --git a/Source/devtools/front_end/utilities.js b/Source/devtools/front_end/utilities.js
index b71989f..b4e955d 100644
--- a/Source/devtools/front_end/utilities.js
+++ b/Source/devtools/front_end/utilities.js
@@ -1391,6 +1391,43 @@
/**
* @constructor
+ * @extends {StringMap.<Set.<!T>>}
+ * @template T
+ */
+var StringMultimap = function()
+{
+ StringMap.call(this);
+}
+
+StringMultimap.prototype = {
+ /**
+ * @param {string} key
+ * @param {T} value
+ */
+ put: function(key, value)
+ {
+ if (key === "__proto__") {
+ if (!this._hasProtoKey) {
+ ++this._size;
+ this._hasProtoKey = true;
+ /** @type {!Set.<T>} */
+ this._protoValue = new Set();
+ }
+ this._protoValue.add(value);
+ return;
+ }
+ if (!Object.prototype.hasOwnProperty.call(this._map, key)) {
+ ++this._size;
+ this._map[key] = new Set();
+ }
+ this._map[key].add(value);
+ },
+
+ __proto__: StringMap.prototype
+}
+
+/**
+ * @constructor
*/
var StringSet = function()
{
@@ -1398,11 +1435,21 @@
this._map = new StringMap();
}
+/**
+ * @param {!Array.<string>} array
+ */
+StringSet.fromArray = function(array)
+{
+ var result = new StringSet();
+ array.forEach(function(item) { result.add(item); });
+ return result;
+}
+
StringSet.prototype = {
/**
* @param {string} value
*/
- put: function(value)
+ add: function(value)
{
this._map.put(value, true);
},