From 6c4277f6117e4864966c9cb58fb835cee8c74a1e Mon Sep 17 00:00:00 2001 From: Steve Lacey Date: Tue, 24 Sep 2019 21:55:42 +0700 Subject: [PATCH] Fetch support (#422) --- src/DebugBar/JavascriptRenderer.php | 26 +++++ src/DebugBar/Resources/debugbar.js | 172 +++++++++++++++++----------- 2 files changed, 134 insertions(+), 64 deletions(-) diff --git a/src/DebugBar/JavascriptRenderer.php b/src/DebugBar/JavascriptRenderer.php index b8cba0aa..16689992 100644 --- a/src/DebugBar/JavascriptRenderer.php +++ b/src/DebugBar/JavascriptRenderer.php @@ -70,6 +70,8 @@ class JavascriptRenderer protected $ajaxHandlerClass = 'PhpDebugBar.AjaxHandler'; + protected $ajaxHandlerBindToFetch = false; + protected $ajaxHandlerBindToJquery = true; protected $ajaxHandlerBindToXHR = false; @@ -477,6 +479,27 @@ public function getAjaxHandlerClass() return $this->ajaxHandlerClass; } + /** + * Sets whether to call bindToFetch() on the ajax handler + * + * @param boolean $bind + */ + public function setBindAjaxHandlerToFetch($bind = true) + { + $this->ajaxHandlerBindToFetch = $bind; + return $this; + } + + /** + * Checks whether bindToFetch() will be called on the ajax handler + * + * @return boolean + */ + public function isAjaxHandlerBoundToFetch() + { + return $this->ajaxHandlerBindToFetch; + } + /** * Sets whether to call bindToJquery() on the ajax handler * @@ -1022,6 +1045,9 @@ protected function getJsInitializationCode() $this->variableName, $this->ajaxHandlerAutoShow ? 'true' : 'false' ); + if ($this->ajaxHandlerBindToFetch) { + $js .= sprintf("%s.ajaxHandler.bindToFetch();\n", $this->variableName); + } if ($this->ajaxHandlerBindToXHR) { $js .= sprintf("%s.ajaxHandler.bindToXHR();\n", $this->variableName); } elseif ($this->ajaxHandlerBindToJquery) { diff --git a/src/DebugBar/Resources/debugbar.js b/src/DebugBar/Resources/debugbar.js index e4f4a7fa..09c3caeb 100644 --- a/src/DebugBar/Resources/debugbar.js +++ b/src/DebugBar/Resources/debugbar.js @@ -95,7 +95,7 @@ if (typeof(PhpDebugBar) == 'undefined') { // ------------------------------------------------------------------ - + /** * Base class for all elements with a visual component * @@ -123,7 +123,7 @@ if (typeof(PhpDebugBar) == 'undefined') { /** * Called after the constructor - * + * * @param {Object} options */ initialize: function(options) { @@ -137,7 +137,7 @@ if (typeof(PhpDebugBar) == 'undefined') { /** * Sets the value of an attribute - * + * * @param {String} attr Can also be an object to set multiple attributes at once * @param {Object} value */ @@ -159,7 +159,7 @@ if (typeof(PhpDebugBar) == 'undefined') { /** * Checks if an attribute exists and is not null - * + * * @param {String} attr * @return {[type]} [description] */ @@ -169,7 +169,7 @@ if (typeof(PhpDebugBar) == 'undefined') { /** * Returns the value of an attribute - * + * * @param {String} attr * @return {Object} */ @@ -181,7 +181,7 @@ if (typeof(PhpDebugBar) == 'undefined') { * Registers a callback function that will be called whenever the value of the attribute changes * * If cb is a jQuery element, text() will be used to fill the element - * + * * @param {String} attr * @param {Function} cb */ @@ -213,7 +213,7 @@ if (typeof(PhpDebugBar) == 'undefined') { * Creates a subclass * * Code from Backbone.js - * + * * @param {Array} props Prototype properties * @return {Function} */ @@ -223,7 +223,7 @@ if (typeof(PhpDebugBar) == 'undefined') { var child = function() { return parent.apply(this, arguments); }; $.extend(child, parent); - var Surrogate = function(){ this.constructor = child; }; + var Surrogate = function() { this.constructor = child; }; Surrogate.prototype = parent.prototype; child.prototype = new Surrogate; $.extend(child.prototype, props); @@ -237,7 +237,7 @@ if (typeof(PhpDebugBar) == 'undefined') { /** * Tab - * + * * A tab is composed of a tab label which is always visible and * a tab panel which is visible only when the tab is active. * @@ -350,7 +350,7 @@ if (typeof(PhpDebugBar) == 'undefined') { /** * Formats the title of a dataset - * + * * @this {DatasetTitleFormater} * @param {String} id * @param {Object} data @@ -379,7 +379,7 @@ if (typeof(PhpDebugBar) == 'undefined') { } else { filename = uri.substr(uri.lastIndexOf('/') + 1); } - + // truncate the filename in the label, if it's too long var maxLength = 150; if (filename.length > maxLength) { @@ -596,7 +596,7 @@ if (typeof(PhpDebugBar) == 'undefined') { */ createTab: function(name, widget, title) { var tab = new Tab({ - title: title || (name.replace(/[_\-]/g, ' ').charAt(0).toUpperCase() + name.slice(1)), + title: title || (name.replace(/[_\-]/g, ' ').charAt(0).toUpperCase() + name.slice(1)), widget: widget }); return this.addTab(name, tab); @@ -652,7 +652,7 @@ if (typeof(PhpDebugBar) == 'undefined') { /** * Adds an indicator - * + * * @this {DebugBar} * @param {String} name Internal name * @param {Indicator} indicator Indicator object @@ -675,7 +675,7 @@ if (typeof(PhpDebugBar) == 'undefined') { /** * Returns a control - * + * * @param {String} name * @return {Object} */ @@ -687,7 +687,7 @@ if (typeof(PhpDebugBar) == 'undefined') { /** * Checks if there's a control under the specified name - * + * * @this {DebugBar} * @param {String} name * @return {Boolean} @@ -698,7 +698,7 @@ if (typeof(PhpDebugBar) == 'undefined') { /** * Checks if a tab with the specified name exists - * + * * @this {DebugBar} * @param {String} name * @return {Boolean} @@ -709,7 +709,7 @@ if (typeof(PhpDebugBar) == 'undefined') { /** * Checks if an indicator with the specified name exists - * + * * @this {DebugBar} * @param {String} name * @return {Boolean} @@ -720,7 +720,7 @@ if (typeof(PhpDebugBar) == 'undefined') { /** * Removes all tabs and indicators from the debug bar and hides it - * + * * @this {DebugBar} */ reset: function() { @@ -737,7 +737,7 @@ if (typeof(PhpDebugBar) == 'undefined') { /** * Open the debug bar and display the specified tab - * + * * @this {DebugBar} * @param {String} name If not specified, display the first tab */ @@ -788,13 +788,13 @@ if (typeof(PhpDebugBar) == 'undefined') { /** * Checks if the panel is minimized - * + * * @return {Boolean} */ isMinimized: function() { return this.$el.hasClass(csscls('minimized')); }, - + /** * Close the debug bar * @@ -809,7 +809,7 @@ if (typeof(PhpDebugBar) == 'undefined') { this.$el.addClass(csscls('closed')); this.recomputeBottomOffset(); }, - + /** * Checks if the panel is closed * @@ -818,7 +818,7 @@ if (typeof(PhpDebugBar) == 'undefined') { isClosed: function() { return this.$el.hasClass(csscls('closed')); }, - + /** * Restore the debug bar * @@ -848,7 +848,7 @@ if (typeof(PhpDebugBar) == 'undefined') { if (this.isClosed()) { return $('body').css('margin-bottom', this.options.bodyMarginBottomHeight || ''); } - + var offset = parseInt(this.$el.height()) + (this.options.bodyMarginBottomHeight || 0); $('body').css('margin-bottom', offset); } @@ -865,7 +865,7 @@ if (typeof(PhpDebugBar) == 'undefined') { * * Example: * {"memory": ["memory.peak_usage_str", "0B"]} - * + * * @this {DebugBar} * @param {Object} map */ @@ -889,7 +889,7 @@ if (typeof(PhpDebugBar) == 'undefined') { * * For this method to be usefull, you need to specify * a dataMap using setDataMap() - * + * * @this {DebugBar} * @param {Object} data * @return {String} Dataset's id @@ -904,10 +904,10 @@ if (typeof(PhpDebugBar) == 'undefined') { * * If more than one dataset are added, the dataset selector * will be displayed. - * + * * For this method to be usefull, you need to specify * a dataMap using setDataMap() - * + * * @this {DebugBar} * @param {Object} data * @param {String} id The name of this set, optional @@ -933,7 +933,7 @@ if (typeof(PhpDebugBar) == 'undefined') { /** * Loads a dataset using the open handler - * + * * @param {String} id * @param {Bool} show Whether to show the new dataset, optional (default: true) */ @@ -950,7 +950,7 @@ if (typeof(PhpDebugBar) == 'undefined') { /** * Returns the data from a dataset - * + * * @this {DebugBar} * @param {String} id * @return {Object} @@ -961,7 +961,7 @@ if (typeof(PhpDebugBar) == 'undefined') { /** * Switch the currently displayed dataset - * + * * @this {DebugBar} * @param {String} id */ @@ -972,7 +972,7 @@ if (typeof(PhpDebugBar) == 'undefined') { /** * Called when the current dataset is modified. - * + * * @this {DebugBar} * @param {Object} data */ @@ -991,7 +991,7 @@ if (typeof(PhpDebugBar) == 'undefined') { /** * Sets the handler to open past dataset - * + * * @this {DebugBar} * @param {object} handler */ @@ -1006,7 +1006,7 @@ if (typeof(PhpDebugBar) == 'undefined') { /** * Returns the handler to open past dataset - * + * * @this {DebugBar} * @return {object} */ @@ -1020,7 +1020,7 @@ if (typeof(PhpDebugBar) == 'undefined') { DebugBar.Indicator = Indicator; // ------------------------------------------------------------------ - + /** * AjaxHandler * @@ -1037,31 +1037,49 @@ if (typeof(PhpDebugBar) == 'undefined') { $.extend(AjaxHandler.prototype, { /** - * Handles an XMLHttpRequest - * + * Handles a Fetch API Response or an XMLHttpRequest + * * @this {AjaxHandler} - * @param {XMLHttpRequest} xhr + * @param {Response|XMLHttpRequest} response * @return {Bool} */ - handle: function(xhr) { - // Check if the debugbar header is available - if (xhr.getAllResponseHeaders().indexOf(this.headerName) === -1){ + handle: function(response) { + // Check if the debugbar header is available + if (this.isFetch(response) && !response.headers.has(this.headerName + '-id')) { + return true; + } else if (this.isXHR(response) && response.getAllResponseHeaders().indexOf(this.headerName) === -1) { return true; } - if (!this.loadFromId(xhr)) { - return this.loadFromData(xhr); + if (!this.loadFromId(response)) { + return this.loadFromData(response); } return true; }, + getHeader: function(response, header) { + if (this.isFetch(response)) { + return response.headers.get(header) + } + + return response.getResponseHeader(header) + }, + + isFetch: function(response) { + return Object.prototype.toString.call(response) == '[object Response]' + }, + + isXHR: function(response) { + return Object.prototype.toString.call(response) == '[object XMLHttpRequest]' + }, + /** * Checks if the HEADER-id exists and loads the dataset using the open handler - * - * @param {XMLHttpRequest} xhr + * + * @param {Response|XMLHttpRequest} response * @return {Bool} */ - loadFromId: function(xhr) { - var id = this.extractIdFromHeaders(xhr); + loadFromId: function(response) { + var id = this.extractIdFromHeaders(response); if (id && this.debugbar.openHandler) { this.debugbar.loadDataSet(id, "(ajax)", undefined, this.autoShow); return true; @@ -1071,22 +1089,22 @@ if (typeof(PhpDebugBar) == 'undefined') { /** * Extracts the id from the HEADER-id - * - * @param {XMLHttpRequest} xhr + * + * @param {Response|XMLHttpRequest} response * @return {String} */ - extractIdFromHeaders: function(xhr) { - return xhr.getResponseHeader(this.headerName + '-id'); + extractIdFromHeaders: function(response) { + return this.getHeader(response, this.headerName + '-id'); }, /** * Checks if the HEADER exists and loads the dataset - * - * @param {XMLHttpRequest} xhr + * + * @param {Response|XMLHttpRequest} response * @return {Bool} */ - loadFromData: function(xhr) { - var raw = this.extractDataFromHeaders(xhr); + loadFromData: function(response) { + var raw = this.extractDataFromHeaders(response); if (!raw) { return false; } @@ -1102,18 +1120,18 @@ if (typeof(PhpDebugBar) == 'undefined') { /** * Extract the data as a string from headers of an XMLHttpRequest - * + * * @this {AjaxHandler} - * @param {XMLHttpRequest} xhr + * @param {Response|XMLHttpRequest} response * @return {string} */ - extractDataFromHeaders: function(xhr) { - var data = xhr.getResponseHeader(this.headerName); + extractDataFromHeaders: function(response) { + var data = this.getHeader(response, this.headerName); if (!data) { return; } for (var i = 1;; i++) { - var header = xhr.getResponseHeader(this.headerName + '-' + i); + var header = this.getHeader(response, this.headerName + '-' + i); if (!header) { break; } @@ -1124,7 +1142,7 @@ if (typeof(PhpDebugBar) == 'undefined') { /** * Parses the string data into an object - * + * * @this {AjaxHandler} * @param {string} data * @return {string} @@ -1133,9 +1151,35 @@ if (typeof(PhpDebugBar) == 'undefined') { return JSON.parse(data); }, + /** + * Attaches an event listener to fetch + * + * @this {AjaxHandler} + */ + bindToFetch: function() { + var self = this; + var proxied = window.fetch; + + if (proxied === undefined && proxied.polyfill !== undefined) { + return; + } + + window.fetch = function () { + var promise = proxied.apply(this, arguments); + + promise.then(function (response) { + self.handle(response); + }, function (e) { + self.handle(response); + }); + + return promise; + }; + }, + /** * Attaches an event listener to jQuery.ajaxComplete() - * + * * @this {AjaxHandler} * @param {jQuery} jq Optional */ @@ -1147,10 +1191,10 @@ if (typeof(PhpDebugBar) == 'undefined') { } }); }, - + /** * Attaches an event listener to XMLHttpRequest - * + * * @this {AjaxHandler} */ bindToXHR: function() {