diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebInspectorUI/UserInterface/Debug/UncaughtExceptionReporter.js | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/WebInspectorUI/UserInterface/Debug/UncaughtExceptionReporter.js')
-rw-r--r-- | Source/WebInspectorUI/UserInterface/Debug/UncaughtExceptionReporter.js | 275 |
1 files changed, 275 insertions, 0 deletions
diff --git a/Source/WebInspectorUI/UserInterface/Debug/UncaughtExceptionReporter.js b/Source/WebInspectorUI/UserInterface/Debug/UncaughtExceptionReporter.js new file mode 100644 index 000000000..43ce09db6 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Debug/UncaughtExceptionReporter.js @@ -0,0 +1,275 @@ +/* + * 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 handleError(error) { + handleUncaughtExceptionRecord({ + message: error.message, + url: parseURL(error.sourceURL).lastPathComponent, + lineNumber: error.line, + columnNumber: error.column, + stack: error.stack, + details: error.details, + }); +} + +function handleUncaughtException(event) { + handleUncaughtExceptionRecord({ + message: event.message, + url: parseURL(event.filename).lastPathComponent, + lineNumber: event.lineno, + columnNumber: event.colno, + stack: typeof event.error === "object" && event.error !== null ? event.error.stack : null, + }); +} + +function handleUncaughtExceptionRecord(exceptionRecord) { + 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 WebInspector.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 (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. + WebInspector.updateWindowTitle(); + + // 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 (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 = WebInspector.frameResourceManager.mainFrame.url; + } catch (e) { } + + 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) + "…" : string; + } + + if (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"); + topLevelItems.push(""); + topLevelItems.push("Uncaught Exceptions:"); + topLevelItems.push(detailsForBugReport); + + let encodedBugDescription = encodeURIComponent(`------- +${topLevelItems.join("\n")} +------- + +* 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. + +* NOTES +Document any additional information that might be useful in resolving the problem, such as screen shots or other included attachments. +`); + 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}&version=WebKit%20Nightly%20Build`; + 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>If you don't think you caused this error to happen, + <a href="${prefilledBugReportLink}">click to file a pre-populated + bug with this information</a>. It is possible that someone else broke it by accident.</dd> + <dt>Oops, can I try again?</dt> + <dd><a href="javascript:window.location.reload()">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); +} + +window.addEventListener("error", handleUncaughtException); +window.handlePromiseException = window.handleInternalException = handleError; + +})(); |