| /* |
| * Copyright (C) 2015 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| * THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| (function() { |
| |
| const windowEvents = ["beforecopy", "copy", "click", "dragover", "focus"]; |
| const documentEvents = ["focus", "blur", "resize", "keydown", "keyup", "mousemove", "pagehide", "contextmenu"]; |
| |
| function stopEventPropagation(event) { |
| if (event.target.classList && event.target.classList.contains("bypass-event-blocking")) |
| return; |
| |
| event.stopPropagation(); |
| } |
| |
| function blockEventHandlers() { |
| // FIXME (151959): text selection on the sheet doesn't work for some reason. |
| for (let name of windowEvents) |
| window.addEventListener(name, stopEventPropagation, true); |
| for (let name of documentEvents) |
| document.addEventListener(name, stopEventPropagation, true); |
| } |
| |
| function unblockEventHandlers() { |
| for (let name of windowEvents) |
| window.removeEventListener(name, stopEventPropagation, true); |
| for (let name of documentEvents) |
| document.removeEventListener(name, stopEventPropagation, true); |
| } |
| |
| function urlLastPathComponent(url) { |
| if (!url) |
| return ""; |
| |
| let slashIndex = url.lastIndexOf("/"); |
| if (slashIndex === -1) |
| return url; |
| |
| return url.slice(slashIndex + 1); |
| } |
| |
| function handleError(error) { |
| handleUncaughtExceptionRecord({ |
| message: error.message, |
| url: urlLastPathComponent(error.sourceURL), |
| lineNumber: error.line, |
| columnNumber: error.column, |
| stack: error.stack, |
| details: error.details, |
| }); |
| } |
| |
| function handleUncaughtException(event) { |
| handleUncaughtExceptionRecord({ |
| message: event.message, |
| url: urlLastPathComponent(event.filename), |
| lineNumber: event.lineno, |
| columnNumber: event.colno, |
| stack: typeof event.error === "object" && event.error !== null ? event.error.stack : null, |
| }); |
| } |
| |
| function handleUnhandledPromiseRejection(event) { |
| handleUncaughtExceptionRecord({ |
| message: event.reason.message, |
| url: urlLastPathComponent(event.reason.sourceURL), |
| lineNumber: event.reason.line, |
| columnNumber: event.reason.column, |
| stack: event.reason.stack, |
| }); |
| } |
| |
| function handleUncaughtExceptionRecord(exceptionRecord) { |
| try { |
| if (!WI.settings.debugEnableUncaughtExceptionReporter.value) |
| return; |
| } catch { } |
| |
| if (!window.__uncaughtExceptions) |
| window.__uncaughtExceptions = []; |
| |
| const loadCompleted = window.__frontendCompletedLoad; |
| const isFirstException = !window.__uncaughtExceptions.length; |
| |
| // If an uncaught exception happens after loading is done, only show |
| // the first such exception. Many others may follow if internal |
| // state has been corrupted, but these are unhelpful to report. |
| if (!loadCompleted || isFirstException) |
| window.__uncaughtExceptions.push(exceptionRecord); |
| |
| // If WI.contentLoaded throws an uncaught exception, then these |
| // listeners will not work correctly because the UI is not fully loaded. |
| // Prevent any event handlers from running in an inconsistent state. |
| if (isFirstException) |
| blockEventHandlers(); |
| |
| if (isFirstException && !loadCompleted) { |
| // Signal that loading is done even though we can't guarantee that |
| // evaluating code on the inspector page will do anything useful. |
| // Without this, the frontend host may never show the window. |
| if (window.InspectorFrontendHost) |
| InspectorFrontendHost.loaded(); |
| |
| // Don't tell InspectorFrontendAPI that loading is done, since it can |
| // clear some of the error boilerplate page by accident. |
| } |
| |
| createErrorSheet(); |
| } |
| |
| function dismissErrorSheet() { |
| unblockEventHandlers(); |
| |
| window.__sheetElement.remove(); |
| window.__sheetElement = null; |
| window.__uncaughtExceptions = []; |
| |
| // Do this last in case WebInspector's internal state is corrupted. |
| try { |
| WI.updateWindowTitle(); |
| } catch { } |
| |
| // FIXME (151959): tell the frontend host to hide a draggable title bar. |
| } |
| |
| function createErrorSheet() { |
| // Early errors like parse errors may happen in the <head>, so attach |
| // a body if none exists yet. Code below expects document.body to exist. |
| if (!document.body) |
| document.write("<body></body></html>"); |
| |
| // FIXME (151959): tell the frontend host to show a draggable title bar. |
| if (window.InspectorFrontendHost) |
| InspectorFrontendHost.inspectedURLChanged("Internal Error"); |
| |
| // Only allow one sheet element at a time. |
| if (window.__sheetElement) { |
| window.__sheetElement.remove(); |
| window.__sheetElement = null; |
| } |
| |
| const loadCompleted = window.__frontendCompletedLoad; |
| let firstException = window.__uncaughtExceptions[0]; |
| |
| // Inlined from Utilities.js, because that file may not have loaded. |
| function insertWordBreakCharacters(text) { |
| return text.replace(/([\/;:\)\]\}&?])/g, "$1\u200b"); |
| } |
| |
| // This trampoline is necessary since none of our functions will be |
| // in scope of an href="javascript:"-style evaluation. |
| function handleLinkClick(event) { |
| if (event.target.tagName !== "A") |
| return; |
| if (event.target.id === "dismiss-error-sheet") |
| dismissErrorSheet(); |
| } |
| |
| function formattedEntry(entry) { |
| const indent = " "; |
| let lines = [`${entry.message} (at ${entry.url}:${entry.lineNumber}:${entry.columnNumber})`]; |
| if (entry.stack) { |
| let stackLines = entry.stack.split(/\n/g); |
| for (let stackLine of stackLines) { |
| let atIndex = stackLine.indexOf("@"); |
| let slashIndex = Math.max(stackLine.lastIndexOf("/"), atIndex); |
| let functionName = stackLine.substring(0, atIndex) || "?"; |
| let location = stackLine.substring(slashIndex + 1, stackLine.length); |
| lines.push(`${indent}${functionName} @ ${location}`); |
| } |
| } |
| |
| if (entry.details) { |
| lines.push(""); |
| lines.push("Additional Details:"); |
| for (let key in entry.details) { |
| let value = entry.details[key]; |
| lines.push(`${indent}${key} --> ${value}`); |
| } |
| } |
| |
| return lines.join("\n"); |
| } |
| |
| let inspectedPageURL = null; |
| try { |
| inspectedPageURL = WI.networkManager.mainFrame.url; |
| } catch { } |
| |
| let topLevelItems = [ |
| `Inspected URL: ${inspectedPageURL || "(unknown)"}`, |
| `Loading completed: ${!!loadCompleted}`, |
| `Frontend User Agent: ${window.navigator.userAgent}`, |
| ]; |
| |
| function stringifyAndTruncateObject(object) { |
| let string = JSON.stringify(object); |
| return string.length > 500 ? string.substr(0, 500) + ellipsis : string; |
| } |
| |
| if (window.InspectorBackend && InspectorBackend.currentDispatchState) { |
| let state = InspectorBackend.currentDispatchState; |
| if (state.event) { |
| topLevelItems.push("Dispatch Source: Protocol Event"); |
| topLevelItems.push(""); |
| topLevelItems.push("Protocol Event:"); |
| topLevelItems.push(stringifyAndTruncateObject(state.event)); |
| } |
| if (state.response) { |
| topLevelItems.push("Dispatch Source: Protocol Command Response"); |
| topLevelItems.push(""); |
| topLevelItems.push("Protocol Command Response:"); |
| topLevelItems.push(stringifyAndTruncateObject(state.response)); |
| } |
| if (state.request) { |
| topLevelItems.push(""); |
| topLevelItems.push("Protocol Command Request:"); |
| topLevelItems.push(stringifyAndTruncateObject(state.request)); |
| } |
| } |
| |
| let formattedErrorDetails = window.__uncaughtExceptions.map((entry) => formattedEntry(entry)); |
| let detailsForBugReport = formattedErrorDetails.map((line) => ` - ${line}`).join("\n"); |
| |
| let encodedBugDescription = encodeURIComponent(`Uncaught Exception in Web Inspector. |
| |
| Steps to Reproduce: |
| 1. What were you doing? Include setup or other preparations to reproduce the exception. |
| 2. Include explicit, accurate, and minimal steps taken. Do not include extraneous or irrelevant steps. |
| 3. What did you expect to have happen? What actually happened? |
| |
| Uncaught Exceptions: |
| ----------------------- |
| ${detailsForBugReport} |
| ----------------------- |
| |
| Notes: |
| ${topLevelItems.join("\n")} |
| `); |
| |
| let encodedBugTitle = encodeURIComponent(`Uncaught Exception: ${firstException.message}`); |
| let encodedInspectedURL = encodeURIComponent(inspectedPageURL || "http://"); |
| let prefilledBugReportLink = `https://bugs.webkit.org/enter_bug.cgi?alias=&assigned_to=webkit-unassigned%40lists.webkit.org&attach_text=&blocked=&bug_file_loc=${encodedInspectedURL}&bug_severity=Normal&bug_status=NEW&comment=${encodedBugDescription}&component=Web%20Inspector&contenttypeentry=&contenttypemethod=autodetect&contenttypeselection=text%2Fplain&data=&dependson=&description=&flag_type-1=X&flag_type-3=X&form_name=enter_bug&keywords=&op_sys=All&priority=P2&product=WebKit&rep_platform=All&short_desc=${encodedBugTitle}`; |
| let detailsForHTML = formattedErrorDetails.map((line) => `<li>${insertWordBreakCharacters(line)}</li>`).join("\n"); |
| |
| let dismissOptionHTML = !loadCompleted ? "" : `<dt>A frivolous exception will not stop me!</dt> |
| <dd><a class="bypass-event-blocking" id="dismiss-error-sheet">Click to close this view</a> and return |
| to the Web Inspector without reloading. However, some things might not work without reloading if the error corrupted the Inspector's internal state.</dd>`; |
| |
| let sheetElement = window.__sheetElement = document.createElement("div"); |
| sheetElement.classList.add("sheet-container"); |
| sheetElement.innerHTML = `<div class="uncaught-exception-sheet"> |
| <h1> |
| <img src="Images/Errors.svg"> |
| Web Inspector encountered an internal error. |
| </h1> |
| <dl> |
| <dd>Usually, this is caused by a syntax error while modifying the Web Inspector |
| UI, or running an updated frontend with out-of-date WebKit build.</dt> |
| <dt>I didn't do anything...?</dt> |
| <dd><a href="${prefilledBugReportLink}" id="uncaught-exception-bug-report-link" class="bypass-event-blocking">Click to file a bug</a> as this is likely a Web Inspector bug.</dd> |
| <dt>Oops, can I try again?</dt> |
| <dd><a href="javascript:InspectorFrontendHost.reopen()" class="bypass-event-blocking">Click to reload the Inspector</a> |
| again after making local changes.</dd> |
| ${dismissOptionHTML} |
| </dl> |
| <h2> |
| <img src="Images/Console.svg"> |
| These uncaught exceptions caused the problem: |
| </h2> |
| <p><ul>${detailsForHTML}</ul></p> |
| </div>`; |
| |
| sheetElement.addEventListener("click", handleLinkClick, true); |
| document.body.appendChild(sheetElement); |
| |
| document.getElementById("uncaught-exception-bug-report-link").addEventListener("click", (event) => { |
| InspectorFrontendHost.openInNewTab(prefilledBugReportLink); |
| event.stopImmediatePropagation(); |
| event.preventDefault(); |
| }); |
| } |
| |
| window.addEventListener("error", handleUncaughtException); |
| window.addEventListener("unhandledrejection", handleUnhandledPromiseRejection); |
| window.handleInternalException = handleError; |
| |
| })(); |