blob: eaa82f663f15647ec3ca3b7fe453a7a7fb52d771 [file] [log] [blame]
// Copyright 2017 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.
export class WorkspaceDiffImpl extends Common.Object {
/**
* @param {!Workspace.Workspace} workspace
*/
constructor(workspace) {
super();
/** @type {!WeakMap<!Workspace.UISourceCode, !UISourceCodeDiff>} */
this._uiSourceCodeDiffs = new WeakMap();
/** @type {!Map<!Workspace.UISourceCode, !Promise>} */
this._loadingUISourceCodes = new Map();
/** @type {!Set<!Workspace.UISourceCode>} */
this._modifiedUISourceCodes = new Set();
workspace.addEventListener(Workspace.Workspace.Events.WorkingCopyChanged, this._uiSourceCodeChanged, this);
workspace.addEventListener(Workspace.Workspace.Events.WorkingCopyCommitted, this._uiSourceCodeChanged, this);
workspace.addEventListener(Workspace.Workspace.Events.UISourceCodeAdded, this._uiSourceCodeAdded, this);
workspace.addEventListener(Workspace.Workspace.Events.UISourceCodeRemoved, this._uiSourceCodeRemoved, this);
workspace.addEventListener(Workspace.Workspace.Events.ProjectRemoved, this._projectRemoved, this);
workspace.uiSourceCodes().forEach(this._updateModifiedState.bind(this));
}
/**
* @param {!Workspace.UISourceCode} uiSourceCode
* @return {!Promise<?Diff.Diff.DiffArray>}
*/
requestDiff(uiSourceCode) {
return this._uiSourceCodeDiff(uiSourceCode).requestDiff();
}
/**
* @param {!Workspace.UISourceCode} uiSourceCode
* @param {function(!Common.Event)} callback
* @param {!Object=} thisObj
*/
subscribeToDiffChange(uiSourceCode, callback, thisObj) {
this._uiSourceCodeDiff(uiSourceCode).addEventListener(Events.DiffChanged, callback, thisObj);
}
/**
* @param {!Workspace.UISourceCode} uiSourceCode
* @param {function(!Common.Event)} callback
* @param {!Object=} thisObj
*/
unsubscribeFromDiffChange(uiSourceCode, callback, thisObj) {
this._uiSourceCodeDiff(uiSourceCode).removeEventListener(Events.DiffChanged, callback, thisObj);
}
/**
* @return {!Array<!Workspace.UISourceCode>}
*/
modifiedUISourceCodes() {
return Array.from(this._modifiedUISourceCodes);
}
/**
* @param {!Workspace.UISourceCode} uiSourceCode
* @return {boolean}
*/
isUISourceCodeModified(uiSourceCode) {
return this._modifiedUISourceCodes.has(uiSourceCode) || this._loadingUISourceCodes.has(uiSourceCode);
}
/**
* @param {!Workspace.UISourceCode} uiSourceCode
* @return {!UISourceCodeDiff}
*/
_uiSourceCodeDiff(uiSourceCode) {
if (!this._uiSourceCodeDiffs.has(uiSourceCode)) {
this._uiSourceCodeDiffs.set(uiSourceCode, new UISourceCodeDiff(uiSourceCode));
}
return this._uiSourceCodeDiffs.get(uiSourceCode);
}
/**
* @param {!Common.Event} event
*/
_uiSourceCodeChanged(event) {
const uiSourceCode = /** @type {!Workspace.UISourceCode} */ (event.data.uiSourceCode);
this._updateModifiedState(uiSourceCode);
}
/**
* @param {!Common.Event} event
*/
_uiSourceCodeAdded(event) {
const uiSourceCode = /** @type {!Workspace.UISourceCode} */ (event.data);
this._updateModifiedState(uiSourceCode);
}
/**
* @param {!Common.Event} event
*/
_uiSourceCodeRemoved(event) {
const uiSourceCode = /** @type {!Workspace.UISourceCode} */ (event.data);
this._removeUISourceCode(uiSourceCode);
}
/**
* @param {!Common.Event} event
*/
_projectRemoved(event) {
const project = /** @type {!Workspace.Project} */ (event.data);
for (const uiSourceCode of project.uiSourceCodes()) {
this._removeUISourceCode(uiSourceCode);
}
}
/**
* @param {!Workspace.UISourceCode} uiSourceCode
*/
_removeUISourceCode(uiSourceCode) {
this._loadingUISourceCodes.delete(uiSourceCode);
const uiSourceCodeDiff = this._uiSourceCodeDiffs.get(uiSourceCode);
if (uiSourceCodeDiff) {
uiSourceCodeDiff._dispose = true;
}
this._markAsUnmodified(uiSourceCode);
}
/**
* @param {!Workspace.UISourceCode} uiSourceCode
*/
_markAsUnmodified(uiSourceCode) {
this._uiSourceCodeProcessedForTest();
if (this._modifiedUISourceCodes.delete(uiSourceCode)) {
this.dispatchEventToListeners(Events.ModifiedStatusChanged, {uiSourceCode, isModified: false});
}
}
/**
* @param {!Workspace.UISourceCode} uiSourceCode
*/
_markAsModified(uiSourceCode) {
this._uiSourceCodeProcessedForTest();
if (this._modifiedUISourceCodes.has(uiSourceCode)) {
return;
}
this._modifiedUISourceCodes.add(uiSourceCode);
this.dispatchEventToListeners(Events.ModifiedStatusChanged, {uiSourceCode, isModified: true});
}
_uiSourceCodeProcessedForTest() {
}
/**
* @param {!Workspace.UISourceCode} uiSourceCode
*/
async _updateModifiedState(uiSourceCode) {
this._loadingUISourceCodes.delete(uiSourceCode);
if (uiSourceCode.project().type() !== Workspace.projectTypes.Network) {
this._markAsUnmodified(uiSourceCode);
return;
}
if (uiSourceCode.isDirty()) {
this._markAsModified(uiSourceCode);
return;
}
if (!uiSourceCode.hasCommits()) {
this._markAsUnmodified(uiSourceCode);
return;
}
const contentsPromise = Promise.all([
this.requestOriginalContentForUISourceCode(uiSourceCode),
uiSourceCode.requestContent().then(deferredContent => deferredContent.content)
]);
this._loadingUISourceCodes.set(uiSourceCode, contentsPromise);
const contents = await contentsPromise;
if (this._loadingUISourceCodes.get(uiSourceCode) !== contentsPromise) {
return;
}
this._loadingUISourceCodes.delete(uiSourceCode);
if (contents[0] !== null && contents[1] !== null && contents[0] !== contents[1]) {
this._markAsModified(uiSourceCode);
} else {
this._markAsUnmodified(uiSourceCode);
}
}
/**
* @param {!Workspace.UISourceCode} uiSourceCode
* @return {!Promise<?string>}
*/
requestOriginalContentForUISourceCode(uiSourceCode) {
return this._uiSourceCodeDiff(uiSourceCode)._originalContent();
}
/**
* @param {!Workspace.UISourceCode} uiSourceCode
* @return {!Promise}
*/
revertToOriginal(uiSourceCode) {
/**
* @param {?string} content
*/
function callback(content) {
if (typeof content !== 'string') {
return;
}
uiSourceCode.addRevision(content);
}
Host.userMetrics.actionTaken(Host.UserMetrics.Action.RevisionApplied);
return this.requestOriginalContentForUISourceCode(uiSourceCode).then(callback);
}
}
export class UISourceCodeDiff extends Common.Object {
/**
* @param {!Workspace.UISourceCode} uiSourceCode
*/
constructor(uiSourceCode) {
super();
this._uiSourceCode = uiSourceCode;
uiSourceCode.addEventListener(Workspace.UISourceCode.Events.WorkingCopyChanged, this._uiSourceCodeChanged, this);
uiSourceCode.addEventListener(Workspace.UISourceCode.Events.WorkingCopyCommitted, this._uiSourceCodeChanged, this);
this._requestDiffPromise = null;
this._pendingChanges = null;
this._dispose = false;
}
_uiSourceCodeChanged() {
if (this._pendingChanges) {
clearTimeout(this._pendingChanges);
this._pendingChanges = null;
}
this._requestDiffPromise = null;
const content = this._uiSourceCode.content();
const delay = (!content || content.length < 65536) ? 0 : UpdateTimeout;
this._pendingChanges = setTimeout(emitDiffChanged.bind(this), delay);
/**
* @this {UISourceCodeDiff}
*/
function emitDiffChanged() {
if (this._dispose) {
return;
}
this.dispatchEventToListeners(Events.DiffChanged);
this._pendingChanges = null;
}
}
/**
* @return {!Promise<?Diff.Diff.DiffArray>}
*/
requestDiff() {
if (!this._requestDiffPromise) {
this._requestDiffPromise = this._innerRequestDiff();
}
return this._requestDiffPromise;
}
/**
* @return {!Promise<?string>}
*/
async _originalContent() {
const originalNetworkContent =
Persistence.networkPersistenceManager.originalContentForUISourceCode(this._uiSourceCode);
if (originalNetworkContent) {
return originalNetworkContent;
}
const content = await this._uiSourceCode.project().requestFileContent(this._uiSourceCode);
return content.content || content.error || '';
}
/**
* @return {!Promise<?Diff.Diff.DiffArray>}
*/
async _innerRequestDiff() {
if (this._dispose) {
return null;
}
const baseline = await this._originalContent();
if (baseline.length > 1024 * 1024) {
return null;
}
// ------------ ASYNC ------------
if (this._dispose) {
return null;
}
let current = this._uiSourceCode.workingCopy();
if (!current && !this._uiSourceCode.contentLoaded()) {
current = (await this._uiSourceCode.requestContent()).content;
}
if (current.length > 1024 * 1024) {
return null;
}
if (this._dispose) {
return null;
}
if (current === null || baseline === null) {
return null;
}
return Diff.Diff.lineDiff(baseline.split(/\r\n|\n|\r/), current.split(/\r\n|\n|\r/));
}
}
/**
* @enum {symbol}
*/
export const Events = {
DiffChanged: Symbol('DiffChanged'),
ModifiedStatusChanged: Symbol('ModifiedStatusChanged')
};
/** @type {?WorkspaceDiffImpl} */
let _instance = null;
/**
* @return {!WorkspaceDiffImpl}
*/
export function workspaceDiff() {
if (!_instance) {
_instance = new WorkspaceDiffImpl(Workspace.workspace);
}
return _instance;
}
export class DiffUILocation {
/**
* @param {!Workspace.UISourceCode} uiSourceCode
*/
constructor(uiSourceCode) {
this.uiSourceCode = uiSourceCode;
}
}
export const UpdateTimeout = 200;
/* Legacy exported object */
self.WorkspaceDiff = self.WorkspaceDiff || {};
/* Legacy exported object */
WorkspaceDiff = WorkspaceDiff || {};
/** @constructor */
WorkspaceDiff.WorkspaceDiff = WorkspaceDiffImpl;
/** @constructor */
WorkspaceDiff.WorkspaceDiff.UISourceCodeDiff = UISourceCodeDiff;
WorkspaceDiff.WorkspaceDiff.UpdateTimeout = UpdateTimeout;
/** @enum {symbol} */
WorkspaceDiff.Events = Events;
WorkspaceDiff.workspaceDiff = workspaceDiff;
/** @constructor */
WorkspaceDiff.DiffUILocation = DiffUILocation;