| // Copyright (C) 2019 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| import * as m from 'mithril'; |
| |
| import {Actions, PostedTrace} from '../common/actions'; |
| |
| import {globals} from './globals'; |
| import {showModal} from './modal'; |
| |
| interface PostedTraceWrapped { |
| perfetto: PostedTrace; |
| } |
| |
| // Returns whether incoming traces should be opened automatically or should |
| // instead require a user interaction. |
| function isTrustedOrigin(origin: string): boolean { |
| const TRUSTED_ORIGINS = [ |
| 'https://chrometto.googleplex.com', |
| 'https://uma.googleplex.com', |
| ]; |
| if (origin === window.origin) return true; |
| if (TRUSTED_ORIGINS.includes(origin)) return true; |
| if (new URL(origin).hostname.endsWith('corp.google.com')) return true; |
| return false; |
| } |
| |
| |
| // The message handler supports loading traces from an ArrayBuffer. |
| // There is no other requirement than sending the ArrayBuffer as the |data| |
| // property. However, since this will happen across different origins, it is not |
| // possible for the source website to inspect whether the message handler is |
| // ready, so the message handler always replies to a 'PING' message with 'PONG', |
| // which indicates it is ready to receive a trace. |
| export function postMessageHandler(messageEvent: MessageEvent) { |
| if (messageEvent.origin === 'https://tagassistant.google.com') { |
| // The GA debugger, does a window.open() and sends messages to the GA |
| // script. Ignore them. |
| return; |
| } |
| |
| if (document.readyState !== 'complete') { |
| console.error('Ignoring message - document not ready yet.'); |
| return; |
| } |
| |
| if (messageEvent.source === null || messageEvent.source !== window.opener) { |
| // This can happen if an extension tries to postMessage. |
| return; |
| } |
| |
| if (!('data' in messageEvent)) { |
| throw new Error('Incoming message has no data property'); |
| } |
| |
| if (messageEvent.data === 'PING') { |
| // Cross-origin messaging means we can't read |messageEvent.source|, but |
| // it still needs to be of the correct type to be able to invoke the |
| // correct version of postMessage(...). |
| const windowSource = messageEvent.source as Window; |
| windowSource.postMessage('PONG', messageEvent.origin); |
| return; |
| } |
| |
| let postedTrace: PostedTrace; |
| |
| if (isPostedTraceWrapped(messageEvent.data)) { |
| postedTrace = sanitizePostedTrace(messageEvent.data.perfetto); |
| } else if (messageEvent.data instanceof ArrayBuffer) { |
| postedTrace = {title: 'External trace', buffer: messageEvent.data}; |
| } else { |
| console.warn( |
| 'Unknown postMessage() event received. If you are trying to open a ' + |
| 'trace via postMessage(), this is a bug in your code. If not, this ' + |
| 'could be due to some Chrome extension.'); |
| console.log('origin:', messageEvent.origin, 'data:', messageEvent.data); |
| return; |
| } |
| |
| if (postedTrace.buffer.byteLength === 0) { |
| throw new Error('Incoming message trace buffer is empty'); |
| } |
| |
| /* Removing this event listener to avoid callers posting the trace multiple |
| * times. If the callers add an event listener which upon receiving 'PONG' |
| * posts the trace to ui.perfetto.dev, the callers can receive multiple 'PONG' |
| * messages and accidentally post the trace multiple times. This was part of |
| * the cause of b/182502595. |
| */ |
| window.removeEventListener('message', postMessageHandler); |
| |
| const openTrace = () => { |
| // For external traces, we need to disable other features such as |
| // downloading and sharing a trace. |
| postedTrace.localOnly = true; |
| globals.dispatch(Actions.openTraceFromBuffer(postedTrace)); |
| }; |
| |
| // If the origin is trusted open the trace directly. |
| if (isTrustedOrigin(messageEvent.origin)) { |
| openTrace(); |
| return; |
| } |
| |
| // If not ask the user if they expect this and trust the origin. |
| showModal({ |
| title: 'Open trace?', |
| content: |
| m('div', |
| m('div', `${messageEvent.origin} is trying to open a trace file.`), |
| m('div', 'Do you trust the origin and want to proceed?')), |
| buttons: [ |
| {text: 'NO', primary: true, id: 'pm_reject_trace', action: () => {}}, |
| {text: 'YES', primary: false, id: 'pm_open_trace', action: openTrace}, |
| ], |
| }); |
| } |
| |
| function sanitizePostedTrace(postedTrace: PostedTrace): PostedTrace { |
| const result: PostedTrace = { |
| title: sanitizeString(postedTrace.title), |
| buffer: postedTrace.buffer |
| }; |
| if (postedTrace.url !== undefined) { |
| result.url = sanitizeString(postedTrace.url); |
| } |
| return result; |
| } |
| |
| function sanitizeString(str: string): string { |
| return str.replace(/[^A-Za-z0-9.\-_#:/?=&;%+ ]/g, ' '); |
| } |
| |
| // tslint:disable:no-any |
| function isPostedTraceWrapped(obj: any): obj is PostedTraceWrapped { |
| const wrapped = obj as PostedTraceWrapped; |
| if (wrapped.perfetto === undefined) { |
| return false; |
| } |
| return wrapped.perfetto.buffer !== undefined && |
| wrapped.perfetto.title !== undefined; |
| } |