1// Copyright (C) 2023 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15// Keep this import first. 16import '../base/static_initializers'; 17import m from 'mithril'; 18import {defer} from '../base/deferred'; 19import {reportError, addErrorHandler, ErrorDetails} from '../base/logging'; 20import {initLiveReloadIfLocalhost} from '../core/live_reload'; 21import {raf} from '../core/raf_scheduler'; 22 23function getRoot() { 24 // Works out the root directory where the content should be served from 25 // e.g. `http://origin/v1.2.3/`. 26 const script = document.currentScript as HTMLScriptElement; 27 28 // Needed for DOM tests, that do not have script element. 29 if (script === null) { 30 return ''; 31 } 32 33 let root = script.src; 34 root = root.substr(0, root.lastIndexOf('/') + 1); 35 return root; 36} 37 38function setupContentSecurityPolicy() { 39 // Note: self and sha-xxx must be quoted, urls data: and blob: must not. 40 const policy = { 41 'default-src': [`'self'`], 42 'script-src': [`'self'`], 43 'object-src': ['none'], 44 'connect-src': [`'self'`], 45 'img-src': [`'self'`, 'data:', 'blob:'], 46 'style-src': [`'self'`], 47 'navigate-to': ['https://*.perfetto.dev', 'self'], 48 }; 49 const meta = document.createElement('meta'); 50 meta.httpEquiv = 'Content-Security-Policy'; 51 let policyStr = ''; 52 for (const [key, list] of Object.entries(policy)) { 53 policyStr += `${key} ${list.join(' ')}; `; 54 } 55 meta.content = policyStr; 56 document.head.appendChild(meta); 57} 58 59function main() { 60 setupContentSecurityPolicy(); 61 62 // Load the css. The load is asynchronous and the CSS is not ready by the time 63 // appendChild returns. 64 const root = getRoot(); 65 const cssLoadPromise = defer<void>(); 66 const css = document.createElement('link'); 67 css.rel = 'stylesheet'; 68 css.href = root + 'perfetto.css'; 69 css.onload = () => cssLoadPromise.resolve(); 70 css.onerror = (err) => cssLoadPromise.reject(err); 71 const favicon = document.head.querySelector('#favicon') as HTMLLinkElement; 72 if (favicon) favicon.href = root + 'assets/favicon.png'; 73 74 document.head.append(css); 75 76 // Add Error handlers for JS error and for uncaught exceptions in promises. 77 addErrorHandler((err: ErrorDetails) => console.log(err.message, err.stack)); 78 window.addEventListener('error', (e) => reportError(e)); 79 window.addEventListener('unhandledrejection', (e) => reportError(e)); 80 81 // Prevent pinch zoom. 82 document.body.addEventListener( 83 'wheel', 84 (e: MouseEvent) => { 85 if (e.ctrlKey) e.preventDefault(); 86 }, 87 {passive: false}, 88 ); 89 90 cssLoadPromise.then(() => onCssLoaded()); 91} 92 93function onCssLoaded() { 94 // Clear all the contents of the initial page (e.g. the <pre> error message) 95 // And replace it with the root <main> element which will be used by mithril. 96 document.body.innerHTML = ''; 97 98 raf.domRedraw = () => { 99 m.render(document.body, m('div')); 100 }; 101 102 initLiveReloadIfLocalhost(false); 103} 104 105main(); 106