Files
react-native-devtools-frontend/front_end/workspace_diff/WorkspaceDiff.js
T
Tim van der Lippe 5511047da1 Fix changes computation for inspector-stylesheet
The inspector-stylesheet is a special virtual file that is used
to keep track of any new CSS rules, which can be added by clicking
the "New Style Rule" button in the Elements panel. However, the
changes computation broke and was no longer showing any changes
to this file.

The most likely culprit is https://crrev.com/c/2378070 which changed
the way errors were handled. The TLDR is that the `baseline` became
an empty string, which is falsy. Hence, the diff computation bailed
and was not showing any diffs.

Instead, we should be explicitly checking of the `baseline` is `null`,
since it is legal to perform a diff computation between an empty
string and a larger string.

R=szuend@chromium.org

Fixed: 1152507
Change-Id: I1aa778e7922586a4381cbc1e1e6e543a5ccffdd9
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2610964
Auto-Submit: Tim van der Lippe <tvanderlippe@chromium.org>
Reviewed-by: Simon Zünd <szuend@chromium.org>
Commit-Queue: Tim van der Lippe <tvanderlippe@chromium.org>
2021-01-11 12:57:37 +00:00

358 lines
10 KiB
JavaScript

// 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.
import * as Common from '../common/common.js';
import * as Diff from '../diff/diff.js';
import * as Host from '../host/host.js';
import * as Persistence from '../persistence/persistence.js';
import * as Workspace from '../workspace/workspace.js';
export class WorkspaceDiffImpl extends Common.ObjectWrapper.ObjectWrapper {
/**
* @param {!Workspace.Workspace.WorkspaceImpl} workspace
*/
constructor(workspace) {
super();
/** @type {!WeakMap<!Workspace.UISourceCode.UISourceCode, !UISourceCodeDiff>} */
this._uiSourceCodeDiffs = new WeakMap();
/** @type {!Map<!Workspace.UISourceCode.UISourceCode, !Promise<?>>} */
this._loadingUISourceCodes = new Map();
/** @type {!Set<!Workspace.UISourceCode.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} uiSourceCode
* @return {!Promise<?Diff.Diff.DiffArray>}
*/
requestDiff(uiSourceCode) {
return this._uiSourceCodeDiff(uiSourceCode).requestDiff();
}
/**
* @param {!Workspace.UISourceCode.UISourceCode} uiSourceCode
* @param {function(!Common.EventTarget.EventTargetEvent):void} callback
* @param {!Object=} thisObj
*/
subscribeToDiffChange(uiSourceCode, callback, thisObj) {
this._uiSourceCodeDiff(uiSourceCode).addEventListener(Events.DiffChanged, callback, thisObj);
}
/**
* @param {!Workspace.UISourceCode.UISourceCode} uiSourceCode
* @param {function(!Common.EventTarget.EventTargetEvent):void} callback
* @param {!Object=} thisObj
*/
unsubscribeFromDiffChange(uiSourceCode, callback, thisObj) {
this._uiSourceCodeDiff(uiSourceCode).removeEventListener(Events.DiffChanged, callback, thisObj);
}
/**
* @return {!Array<!Workspace.UISourceCode.UISourceCode>}
*/
modifiedUISourceCodes() {
return Array.from(this._modifiedUISourceCodes);
}
/**
* @param {!Workspace.UISourceCode.UISourceCode} uiSourceCode
* @return {boolean}
*/
isUISourceCodeModified(uiSourceCode) {
return this._modifiedUISourceCodes.has(uiSourceCode) || this._loadingUISourceCodes.has(uiSourceCode);
}
/**
* @param {!Workspace.UISourceCode.UISourceCode} uiSourceCode
* @return {!UISourceCodeDiff}
*/
_uiSourceCodeDiff(uiSourceCode) {
let diff = this._uiSourceCodeDiffs.get(uiSourceCode);
if (!diff) {
diff = new UISourceCodeDiff(uiSourceCode);
this._uiSourceCodeDiffs.set(uiSourceCode, diff);
}
return diff;
}
/**
* @param {!Common.EventTarget.EventTargetEvent} event
*/
_uiSourceCodeChanged(event) {
const uiSourceCode = /** @type {!Workspace.UISourceCode.UISourceCode} */ (event.data.uiSourceCode);
this._updateModifiedState(uiSourceCode);
}
/**
* @param {!Common.EventTarget.EventTargetEvent} event
*/
_uiSourceCodeAdded(event) {
const uiSourceCode = /** @type {!Workspace.UISourceCode.UISourceCode} */ (event.data);
this._updateModifiedState(uiSourceCode);
}
/**
* @param {!Common.EventTarget.EventTargetEvent} event
*/
_uiSourceCodeRemoved(event) {
const uiSourceCode = /** @type {!Workspace.UISourceCode.UISourceCode} */ (event.data);
this._removeUISourceCode(uiSourceCode);
}
/**
* @param {!Common.EventTarget.EventTargetEvent} event
*/
_projectRemoved(event) {
const project = /** @type {!Workspace.Workspace.Project} */ (event.data);
for (const uiSourceCode of project.uiSourceCodes()) {
this._removeUISourceCode(uiSourceCode);
}
}
/**
* @param {!Workspace.UISourceCode.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} uiSourceCode
*/
_markAsUnmodified(uiSourceCode) {
this._uiSourceCodeProcessedForTest();
if (this._modifiedUISourceCodes.delete(uiSourceCode)) {
this.dispatchEventToListeners(Events.ModifiedStatusChanged, {uiSourceCode, isModified: false});
}
}
/**
* @param {!Workspace.UISourceCode.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} uiSourceCode
*/
async _updateModifiedState(uiSourceCode) {
this._loadingUISourceCodes.delete(uiSourceCode);
if (uiSourceCode.project().type() !== Workspace.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} uiSourceCode
* @return {!Promise<?string>}
*/
requestOriginalContentForUISourceCode(uiSourceCode) {
return this._uiSourceCodeDiff(uiSourceCode)._originalContent();
}
/**
* @param {!Workspace.UISourceCode.UISourceCode} uiSourceCode
* @return {!Promise<void>}
*/
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.ObjectWrapper.ObjectWrapper {
/**
* @param {!Workspace.UISourceCode.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.NetworkPersistenceManager.instance().originalContentForUISourceCode(
this._uiSourceCode);
if (originalNetworkContent) {
return originalNetworkContent;
}
const content = await this._uiSourceCode.project().requestFileContent(this._uiSourceCode);
return content.content || ('error' in content && content.error) || '';
}
/**
* @return {!Promise<?Diff.Diff.DiffArray>}
*/
async _innerRequestDiff() {
if (this._dispose) {
return null;
}
const baseline = await this._originalContent();
if (baseline === null) {
return null;
}
if (baseline.length > 1024 * 1024) {
return null;
}
// ------------ ASYNC ------------
if (this._dispose) {
return null;
}
let current = this._uiSourceCode.workingCopy();
if (!current && !this._uiSourceCode.contentLoaded()) {
current = /** @type {string} */ ((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.DiffWrapper.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.WorkspaceImpl.instance());
}
return _instance;
}
export class DiffUILocation {
/**
* @param {!Workspace.UISourceCode.UISourceCode} uiSourceCode
*/
constructor(uiSourceCode) {
this.uiSourceCode = uiSourceCode;
}
}
export const UpdateTimeout = 200;