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'; 17 18import m from 'mithril'; 19 20import {defer} from '../base/deferred'; 21import {reportError, addErrorHandler, ErrorDetails} from '../base/logging'; 22import {initLiveReloadIfLocalhost} from '../core/live_reload'; 23import {raf} from '../core/raf_scheduler'; 24import {setScheduleFullRedraw} from '../widgets/raf'; 25 26function getRoot() { 27 // Works out the root directory where the content should be served from 28 // e.g. `http://origin/v1.2.3/`. 29 const script = document.currentScript as HTMLScriptElement; 30 31 // Needed for DOM tests, that do not have script element. 32 if (script === null) { 33 return ''; 34 } 35 36 let root = script.src; 37 root = root.substr(0, root.lastIndexOf('/') + 1); 38 return root; 39} 40 41function setupContentSecurityPolicy() { 42 // Note: self and sha-xxx must be quoted, urls data: and blob: must not. 43 const policy = { 44 'default-src': [ 45 `'self'`, 46 ], 47 'script-src': [ 48 `'self'`, 49 ], 50 'object-src': ['none'], 51 'connect-src': [ 52 `'self'`, 53 ], 54 'img-src': [ 55 `'self'`, 56 'data:', 57 'blob:', 58 ], 59 'style-src': [ 60 `'self'`, 61 ], 62 'navigate-to': ['https://*.perfetto.dev', 'self'], 63 }; 64 const meta = document.createElement('meta'); 65 meta.httpEquiv = 'Content-Security-Policy'; 66 let policyStr = ''; 67 for (const [key, list] of Object.entries(policy)) { 68 policyStr += `${key} ${list.join(' ')}; `; 69 } 70 meta.content = policyStr; 71 document.head.appendChild(meta); 72} 73 74function main() { 75 // Wire up raf for widgets. 76 setScheduleFullRedraw(() => raf.scheduleFullRedraw()); 77 78 setupContentSecurityPolicy(); 79 80 // Load the css. The load is asynchronous and the CSS is not ready by the time 81 // appendChild returns. 82 const root = getRoot(); 83 const cssLoadPromise = defer<void>(); 84 const css = document.createElement('link'); 85 css.rel = 'stylesheet'; 86 css.href = root + 'perfetto.css'; 87 css.onload = () => cssLoadPromise.resolve(); 88 css.onerror = (err) => cssLoadPromise.reject(err); 89 const favicon = document.head.querySelector('#favicon') as HTMLLinkElement; 90 if (favicon) favicon.href = root + 'assets/favicon.png'; 91 92 document.head.append(css); 93 94 // Add Error handlers for JS error and for uncaught exceptions in promises. 95 addErrorHandler((err: ErrorDetails) => console.log(err.message, err.stack)); 96 window.addEventListener('error', (e) => reportError(e)); 97 window.addEventListener('unhandledrejection', (e) => reportError(e)); 98 99 // Prevent pinch zoom. 100 document.body.addEventListener('wheel', (e: MouseEvent) => { 101 if (e.ctrlKey) e.preventDefault(); 102 }, {passive: false}); 103 104 cssLoadPromise.then(() => onCssLoaded()); 105} 106 107function onCssLoaded() { 108 // Clear all the contents of the initial page (e.g. the <pre> error message) 109 // And replace it with the root <main> element which will be used by mithril. 110 document.body.innerHTML = ''; 111 112 raf.domRedraw = () => { 113 m.render(document.body, m('div')); 114 }; 115 116 initLiveReloadIfLocalhost(false); 117} 118 119main(); 120