Skip to content

Commit

Permalink
Fix: Show updates during analysis
Browse files Browse the repository at this point in the history
Add a "working" graphic and a periodically updating status
message to indicate progress during analysis.

Did not add an explicit progress bar as hints run in parallel and
we don't have a clear indicator of how long a scan will take.

Also clean up some dead code and variable names.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Fix #1667
Close #1709
  • Loading branch information
antross authored and molant committed Jan 18, 2019
1 parent 35ce084 commit acdadd4
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 76 deletions.
2 changes: 1 addition & 1 deletion packages/extension-browser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@
"build-release": "npm run clean && npm run build:assets && tsc --inlineSourceMap false --removeComments true",
"build:assets": "cpx \"./{src,tests}/**/{!(*.ts),.!(ts)}\" dist",
"build:ts": "tsc -b",
"build:webpack": "webpack && cpx \"./src/**/*.{html,json,png}\" dist/bundle",
"build:webpack": "webpack && cpx \"./src/**/*.{html,json,png,svg}\" dist/bundle",
"clean": "rimraf dist",
"lint": "npm-run-all lint:*",
"lint:js": "eslint . --cache --ext js --ext md --ext ts --ignore-path ../../.eslintignore --report-unused-disable-directives",
Expand Down
69 changes: 53 additions & 16 deletions packages/extension-browser/src/devtools/panel/panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import analyzeView from './views/pages/analyze';
import configurationView from './views/pages/configuration';
import resultsView from './views/pages/results';

import { addMessageListener, sendMessage } from './utils/messaging';
import { addMessageListener, removeMessageListener, sendMessage } from './utils/messaging';
import { addNetworkListeners, removeNetworkListeners } from './utils/network';
import { syncTheme } from './utils/themes';

Expand All @@ -17,41 +17,78 @@ const render = (fragment: DocumentFragment) => {
document.body.appendChild(fragment);
};

let delayResultsUntil = Date.now();

/**
* Wait 5s after called before showing results.
* Called when messages change in the "Analyzing" page.
* Gives users a chance to read the last message before it disappears.
* This does not delay showing to the "Configuration" page on cancel.
*/
const delayResults = () => {
delayResultsUntil = Date.now() + 5000;
};

let resultsTimeout: NodeJS.Timeout;

/**
* Render the provided view, cancelling any scheduled renders.
* This ensures a delayed render of the results view doesn't suddenly
* get switched in after a user explicitly cancels analysis.
*/
const show = (fragment: DocumentFragment) => {
clearTimeout(resultsTimeout);
render(fragment);
};

/** Handle results received from a scan. */
const onMessage = (message: Events) => {
if (message.results) {
const results = message.results;

// Stop listening for results and network requests.
removeMessageListener(onMessage);
removeNetworkListeners();

const showResults = () => {
show(resultsView({onRestartClick: onCancel, results})); // eslint-disable-line
};

// Display the "Results" page after any pending delays.
if (Date.now() < delayResultsUntil) {
resultsTimeout = setTimeout(showResults, delayResultsUntil - Date.now());
} else {
showResults();
}
}
};

/** Handle the user cancelling a scan or preparing to configure a new one. */
const onCancel = () => {
// Notify the background script that we're done scanning (in case a scan is still in-progress).
sendMessage({ done: true });

// Stop listening for network requests.
// Stop listening for results and network requests.
removeMessageListener(onMessage);
removeNetworkListeners();

// Display the "Configuration" page.
render(configurationView({ onAnalyzeClick: onStart })); // eslint-disable-line
show(configurationView({ onAnalyzeClick: onStart })); // eslint-disable-line
};

/** Handle the user request to start a scan. */
const onStart = (config: Config) => {
// Notify the background script to begin scanning with the chosen configuration.
sendMessage({ enable: config });

// Listen for network requests (to create `fetch::end::*` events).
// Listen for scan results and network requests (to create `fetch::end::*` events).
addMessageListener(onMessage);
addNetworkListeners();

// Display the "Analyzing" page.
render(analyzeView({ onCancelClick: onCancel }));
show(analyzeView({ onCancelClick: onCancel, onMessageChange: delayResults }));
};

/** Handle results received from a scan. */
addMessageListener((message: Events) => {
if (message.results) {
// Stop listening for network requests.
removeNetworkListeners();

// Display the "Results" page.
render(resultsView({onRestartClick: onCancel, results: message.results}));
}
});

// Start in the stopped state (on the "Configuration" page).
onCancel();

Expand Down
25 changes: 25 additions & 0 deletions packages/extension-browser/src/devtools/panel/utils/inspire.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
const messages = [
`78.6% of websites use a JS library with known vulnerabilities`,
`The average load time for mobile sites is 19 seconds over 3G connections`,
`53% of mobile site visits are abandoned if pages take longer than 3 seconds to load`,
`84% of websites perform a redirect for at least one resource`,
`97.5% of sites forget to compress one or more resources`,
`72.8% of sites compress at least one resource they shouldn't`,
`52% of resources aren't cacheable`,
`The median site size is 1,731KB`,
`1MB of JavaScript takes more time to process than 1MB of a JPEG file`
];

let remaining = [...messages];

const inspire = (): string => {
if (!remaining.length) {
remaining = [...messages];
}

const index = Math.floor(Math.random() * remaining.length);

return remaining.splice(index, 1)[0];
};

export default inspire;
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@
background-color: var(--bg-alt-color);
}

.analyze__image {
max-width: 25rem;
}

.analyze__message {
margin-top: -0.5rem;
}

.analyze__status {
padding: 2em;
text-align: center;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import html from '../../../../shared/html-literal';
import { Events } from '../../../../shared/types';

import inspire from '../../utils/inspire';
import { addMessageListener, removeMessageListener } from '../../utils/messaging';

import headerView from '../partials/header';

Expand All @@ -7,24 +11,49 @@ import './analyze.css';

type Props = {
onCancelClick: Function;
onMessageChange: Function;
};

const onSubmit = (event: Event) => {
event.preventDefault();
};

export default function view({ onCancelClick }: Props) {
return html`
/** Switch between messages periodically while we wait for a scan to finish. */
const rotateMessages = (element: Element, onMessageChange: Function) => {
const interval = setInterval(() => {
element.textContent = inspire();
onMessageChange();
}, 7500);

const stopRotating = (message: Events) => {
removeMessageListener(stopRotating);
if (message.results) {
clearInterval(interval);
}
};

onMessageChange();
addMessageListener(stopRotating);
};

export default function view({ onCancelClick, onMessageChange }: Props) {
const fragment = html`
<form class="analyze page" onsubmit=${onSubmit}>
${headerView({analyzeDisabled: true, analyzeText: 'Analyze website'})}
<h1 class="page__header">
Analyzing...
</h1>
<section class="analyze__status">
<img class="analyze__image" src="/nellie-working.svg" />
<p class="analyze__message">Analyzing...</p>
<button class="page__button page__button--primary analyze__cancel-button" onclick=${onCancelClick}>
Cancel analysis
</button>
</section>
</form>
`;

rotateMessages(fragment.querySelector('.analyze__message')!, onMessageChange);

return fragment;
}
1 change: 1 addition & 0 deletions packages/extension-browser/src/nellie-working.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 12 additions & 12 deletions packages/extension-browser/src/shared/globals.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
declare const browser: typeof chrome;

// Normalize access to extension APIs across browsers.
const b: typeof chrome = typeof browser !== 'undefined' ? browser : chrome;
const _browser: typeof chrome = typeof browser !== 'undefined' ? browser : chrome;

// Include references to web browser globals to facilitate mocks during testing.
const d = document;
const e = eval; // eslint-disable-line
const f = fetch;
const l = location;
const w = window;
const _document = document;
const _eval = eval; // eslint-disable-line no-eval
const _fetch = fetch;
const _location = location;
const _window = window;

export {
b as browser,
d as document,
e as eval,
f as fetch,
l as location,
w as window
_browser as browser,
_document as document,
_eval as eval,
_fetch as fetch,
_location as location,
_window as window
};
45 changes: 0 additions & 45 deletions packages/extension-browser/src/shared/types.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,5 @@
import { FetchEnd, FetchStart, Problem } from 'hint/dist/src/lib/types';

// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest/HttpHeaders
export type HttpHeaders = {
name: string;
value?: string;
binaryValue?: number[];
}[];

/*
* The union of `details` types for webRequest events (as they mostly overlap):
* https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest
*/
export type Details = {
documentUrl: string;
frameId: number;
fromCache: boolean;
ip: string;
method: string;
originUrl: string;
parentFrameId: number;
proxyInfo: {
host: string;
port: number;
type: string;
username: string;
proxyDNS: boolean;
failoverTimeout: number;
};
redirectUrl: string;
requestId: string;
requestHeaders: HttpHeaders;
responseHeaders: HttpHeaders;
statusCode: number;
statusLine: string;
tabId: number;
timeStamp: number;
type: chrome.webRequest.ResourceType;
url: string;
};

export type Config = {
categories?: string[];
browserslist?: string;
Expand All @@ -58,11 +19,6 @@ export type CategoryResults = {
passed: number;
};

export type ResponseBody = {
content: string;
url: string;
};

export type Results = {
categories: CategoryResults[];
};
Expand All @@ -74,7 +30,6 @@ export type Events = {
done?: boolean;
ready?: boolean;
requestConfig?: boolean;
responseBody?: ResponseBody;
results?: Results;
tabId?: number;
};

0 comments on commit acdadd4

Please sign in to comment.