src/js/webview/webview.js (137 lines of code) (raw):
/**
* Copyright 2017-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import { CSSSelectorResolver } from './CSSSelectorResolver';
import { ipcRenderer } from 'electron';
import { BrowserMessageTypes } from '../models/BrowserMessage';
import { WebviewStateMachine, WebviewStates } from './WebviewStateMachine';
import { WebviewUtils } from './WebviewUtils';
import type { WebviewState } from './WebviewStateMachine';
import type { BrowserMessage } from '../models/BrowserMessage';
// Load Filters
import './filters/all.selector.filter';
import './filters/GlobalRule.article.body.filter';
import './filters/GlobalRule.author.name.filter';
//
// Listen to messages from the Browser
//
ipcRenderer.on('message', (event, message) => receiveMessage(message));
function receiveMessage(message: BrowserMessage): void {
if (document.body == null) {
return;
}
switch (message.type) {
case BrowserMessageTypes.HIGHLIGHT_ELEMENT:
WebviewStateMachine.state = WebviewStates.DEFAULT;
WebviewUtils.highlightElementsBySelector(
message.selector,
message.contextSelector
);
break;
case BrowserMessageTypes.HIGHLIGHT_WARNING_ELEMENTS:
WebviewStateMachine.state = WebviewStates.DEFAULT;
WebviewUtils.highlightWarningElementsBySelector(message.selector);
break;
case BrowserMessageTypes.SELECT_ELEMENT:
WebviewUtils.clearHighlights();
WebviewStateMachine.contextSelector = message.selector;
WebviewStateMachine.passThroughSelectors = message.passThroughSelectors;
WebviewStateMachine.fieldName = message.fieldName;
if (message.multiple) {
WebviewStateMachine.state = WebviewStates.SELECTING_MULTIPLE;
} else {
WebviewStateMachine.state = WebviewStates.SELECTING_ELEMENT;
}
break;
case BrowserMessageTypes.CLEAR_HIGHLIGHTS:
WebviewStateMachine.state = WebviewStates.DEFAULT;
WebviewUtils.clearHighlights();
break;
case BrowserMessageTypes.FETCH_ATTRIBUTES:
fetchAttributes(message.selector, message.contextSelector);
break;
}
}
function fetchAttributes(selector: string, contextSelector: string) {
if (selector == '') {
return;
}
const contextElements = document.querySelectorAll(contextSelector);
for (let context of contextElements) {
const elements = context.querySelectorAll(selector);
const count = elements.length;
let attributes = null;
if (context.matches(selector)) {
// Handle selecting the context itself, not a child node
// Ex: selecting a/href as the href of a link
attributes = WebviewUtils.getAttributes(context);
} else if (count > 0) {
// Returns the attributes of the first matched element
attributes = WebviewUtils.getAttributes(elements.item(0));
}
if (attributes != null) {
ipcRenderer.sendToHost('message', {
type: BrowserMessageTypes.ATTRIBUTES_RETRIEVED,
selector,
count,
attributes,
});
return;
}
}
}
//
// Listen to changes in the webview state
//
WebviewStateMachine.onChange(onChangeState);
function onChangeState(oldState: WebviewState, newState: WebviewState) {
const body = document.body;
if (body == null) {
return;
}
switch (newState) {
case WebviewStates.SELECTING_ELEMENT:
case WebviewStates.SELECTING_MULTIPLE:
WebviewUtils.startSelecting(WebviewStateMachine.contextSelector);
document.addEventListener('click', handleSelectElement);
document.addEventListener('mouseover', hightlightOnHover);
break;
case WebviewStates.DEFAULT:
WebviewUtils.stopSelecting();
document.removeEventListener('click', handleSelectElement);
document.removeEventListener('mouseover', hightlightOnHover);
break;
}
}
function handleSelectElement(event: MouseEvent) {
let element = event.target;
if (!(element instanceof Element)) {
return;
}
const fieldName = WebviewStateMachine.fieldName;
if (fieldName == null) {
return;
}
const contextSelector = WebviewStateMachine.contextSelector;
// Filter element
element = WebviewUtils.filterElement(element);
// Resolve the CSS selector for the selected element
const selectors: string[] = CSSSelectorResolver.resolve(
element,
WebviewStateMachine.state === WebviewStates.SELECTING_MULTIPLE,
contextSelector,
fieldName
);
ipcRenderer.sendToHost('message', {
type: BrowserMessageTypes.ELEMENT_SELECTED,
selectors,
});
const selector = selectors[0];
if (selector != null) {
if (
WebviewStateMachine.state ===
BrowserMessageTypes.HIGHLIGHT_WARNING_ELEMENTS
) {
WebviewUtils.highlightWarningElementsBySelector(selector);
} else {
WebviewUtils.highlightElementsBySelector(
selector,
WebviewStateMachine.contextSelector
);
}
}
WebviewStateMachine.state = WebviewStates.DEFAULT;
// Prevent navigation to links when clicked
event.preventDefault();
}
function hightlightOnHover(event: MouseEvent) {
WebviewUtils.clearHighlights();
let element = event.target;
if (element instanceof Element) {
WebviewUtils.hoverElement(WebviewUtils.filterElement(element));
}
}