Skip to content

Commit

Permalink
Preserve Webview Scroll Position
Browse files Browse the repository at this point in the history
Fixes microsoft#22995

**Bug**
If you switch away from an editor that users a webview, the scroll position is currently not preserved. This effects our release notes and the markdown preview. The root cause is that the webview is disposed of when the view is hidden.

**Fix**
Add some presisted state to track scrollProgress through the webview. Use this state in the standard html editor and in the release notes.
  • Loading branch information
mjbvz committed May 17, 2017
1 parent e0969a5 commit ec78a13
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 12 deletions.
12 changes: 10 additions & 2 deletions src/vs/workbench/parts/html/browser/htmlPreviewPart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,19 @@ export class HtmlPreviewPart extends BaseEditor {
if (!this._webview) {
this._webview = new Webview(this._container, this.partService.getContainer(Parts.EDITOR_PART));
this._webview.baseUrl = this._baseUrl && this._baseUrl.toString(true);
if (this.input && this.input instanceof HtmlInput) {
this.webview.initialScrollProgress = this.input.scrollYPercentage;
}

this._webviewDisposables = [
this._webview,
this._webview.onDidClickLink(uri => this._openerService.open(uri)),
this._webview.onDidLoadContent(data => this.telemetryService.publicLog('previewHtml', data.stats))
this._webview.onDidLoadContent(data => this.telemetryService.publicLog('previewHtml', data.stats)),
this._webview.onDidScroll(data => {
if (this.input && this.input instanceof HtmlInput) {
this.input.updateScroll(data.scrollYPercentage);
}
})
];
}
return this._webview;
Expand Down Expand Up @@ -187,7 +195,7 @@ export class HtmlPreviewPart extends BaseEditor {
});
this.webview.baseUrl = resourceUri.toString(true);
this.webview.contents = this.model.getLinesContent();

this.webview.initialScrollProgress = input.scrollYPercentage;
return undefined;
});
});
Expand Down
61 changes: 52 additions & 9 deletions src/vs/workbench/parts/html/browser/webview-pre.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
const ipcRenderer = require('electron').ipcRenderer;


const initData = {};
const initData = {
initialScrollProgress: undefined
};

function styleBody(body) {
if (!body) {
Expand All @@ -26,10 +28,14 @@
return document.getElementById('_target');
}

/**
* @param {MouseEvent} event
*/
function handleInnerClick(event) {
if (!event || !event.view || !event.view.document) {
return;
}
/** @type {any} */
var node = event.target;
while (node) {
if (node.tagName === "A" && node.href) {
Expand All @@ -51,6 +57,27 @@
}
}

var isHandlingScroll = false;
function handleInnerScroll(event) {
if (isHandlingScroll) {
return;
}

const progress = event.target.body.scrollTop / event.target.body.clientHeight;
if (isNaN(progress)) {
return;
}

isHandlingScroll = true;
window.requestAnimationFrame(function () {
try {
ipcRenderer.sendToHost('did-scroll', progress);
} catch (e) {
// noop
}
isHandlingScroll = false;
});
}

document.addEventListener("DOMContentLoaded", function (event) {
ipcRenderer.on('baseUrl', function (event, value) {
Expand Down Expand Up @@ -123,7 +150,23 @@
}

// keep current scrollTop around and use later
const scrollTop = frame && frame.contentDocument && frame.contentDocument.body ? frame.contentDocument.body.scrollTop : 0;
let setInitialScrollPosition;
if (frame) {
const scrollY = frame.contentDocument && frame.contentDocument.body ? frame.contentDocument.body.scrollTop : 0;
setInitialScrollPosition = function (body) {
body.scrollTop = scrollY;
}
} else {
// First load
setInitialScrollPosition = function (body, window) {
body.scrollTop = 0;
if (!isNaN(initData.initialScrollProgress)) {
window.addEventListener('load', function() {
body.scrollTop = body.clientHeight * initData.initialScrollProgress
});
}
}
}

const newFrame = document.createElement('iframe');
newFrame.setAttribute('id', '_target');
Expand All @@ -140,17 +183,12 @@
};

newFrame.contentWindow.addEventListener('DOMContentLoaded', function (e) {
/**
* @type {any}
*/
/** @type {any} */
const contentDocument = e.target;
if (contentDocument.body) {

// Workaround for https://github.com/Microsoft/vscode/issues/12865
// check new scrollTop and reset if neccessary
if (scrollTop !== contentDocument.body.scrollTop) {
contentDocument.body.scrollTop = scrollTop;
}
setInitialScrollPosition(contentDocument.body, this);

// Bubble out link clicks
contentDocument.body.addEventListener('click', handleInnerClick);
Expand All @@ -166,6 +204,7 @@
const newFrame = getTarget();
if (newFrame.contentDocument === contentDocument) {
newFrame.style.display = 'block';
this.addEventListener('scroll', handleInnerScroll);
}
});

Expand All @@ -186,6 +225,10 @@
}
});

ipcRenderer.on('initial-scroll-position', function (event, progress) {
initData.initialScrollProgress = progress;
});

// forward messages from the embedded iframe
window.onmessage = function (message) {
ipcRenderer.sendToHost(message.data.command, message.data.data);
Expand Down
15 changes: 15 additions & 0 deletions src/vs/workbench/parts/html/browser/webview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ export default class Webview {
private _onDidClickLink = new Emitter<URI>();
private _onDidLoadContent = new Emitter<{ stats: any }>();

private _onDidScroll = new Emitter<{ scrollYPercentage: number }>();

constructor(
private parent: HTMLElement,
private _styleElement: Element
Expand Down Expand Up @@ -108,6 +110,11 @@ export default class Webview {
this.layout();
return;
}

if (event.channel === 'did-scroll') {
this._onDidScroll.fire({ scrollYPercentage: event.args[0] });
return;
}
})
];

Expand All @@ -134,12 +141,20 @@ export default class Webview {
return this._onDidLoadContent.event;
}

get onDidScroll(): Event<{ scrollYPercentage: number }> {
return this._onDidScroll.event;
}

private _send(channel: string, ...args: any[]): void {
this._ready
.then(() => this._webview.send(channel, ...args))
.done(void 0, console.error);
}

set initialScrollProgress(value: number) {
this._send('initial-scroll-position', value);
}

set contents(value: string[]) {
this._send('content', value);
}
Expand Down
8 changes: 7 additions & 1 deletion src/vs/workbench/parts/html/common/htmlInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,11 @@
import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';

export class HtmlInput extends ResourceEditorInput {
// just a marker class
private _scrollYPercentage: number = 0;

get scrollYPercentage(): number { return this._scrollYPercentage; }

updateScroll(scrollYPercentage: number) {
this._scrollYPercentage = scrollYPercentage;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,18 @@ export class ReleaseNotesEditor extends BaseEditor {
.then<void>(body => {
this.webview = new WebView(this.content, this.partService.getContainer(Parts.EDITOR_PART));
this.webview.baseUrl = `https://code.visualstudio.com/raw/`;
if (this.input && this.input instanceof ReleaseNotesInput) {
this.webview.initialScrollProgress = this.input.scrollYPercentage;
}
this.webview.style(this.themeService.getTheme());
this.webview.contents = [body];

this.webview.onDidClickLink(link => this.openerService.open(link), null, this.contentDisposables);
this.webview.onDidScroll(event => {
if (this.input && this.input instanceof ReleaseNotesInput) {
this.input.updateScroll(event.scrollYPercentage);
}
}, null, this.contentDisposables);
this.themeService.onThemeChange(themeId => this.webview.style(themeId), null, this.contentDisposables);
this.contentDisposables.push(this.webview);
this.contentDisposables.push(toDisposable(() => this.webview = null));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ export class ReleaseNotesInput extends EditorInput {

static get ID() { return 'workbench.releaseNotes.input'; }

private _scrollYPercentage: number = 0;

get version(): string { return this._version; }
get text(): string { return this._text; }
get scrollYPercentage(): number { return this._scrollYPercentage; }

constructor(private _version: string, private _text: string) {
super();
Expand Down Expand Up @@ -44,4 +47,8 @@ export class ReleaseNotesInput extends EditorInput {
supportsSplitEditor(): boolean {
return false;
}

updateScroll(scrollYPercentage: number) {
this._scrollYPercentage = scrollYPercentage;
}
}

0 comments on commit ec78a13

Please sign in to comment.