| /* |
| * Copyright 2013 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /** |
| * @fileoverview Code for detecting and sending to the server the critical CSS |
| * selectors (selectors applying to any DOM elements on the page) on the client |
| * side. To use, this script should be injected right before </body> with a call |
| * to pagespeed.criticalCssBeaconInit(...) appended to it. |
| * |
| * @author jud@google.com (Jud Porter) |
| */ |
| |
| goog.require('pagespeedutils'); |
| |
| // Exporting functions using quoted attributes to prevent js compiler from |
| // renaming them. |
| // See http://code.google.com/closure/compiler/docs/api-tutorial3.html#dangers |
| window['pagespeed'] = window['pagespeed'] || {}; |
| var pagespeed = window['pagespeed']; |
| |
| |
| |
| /** |
| * @constructor |
| * @param {string} beaconUrl The URL on the server to send the beacon to. |
| * @param {string} htmlUrl Url of the page the beacon is being inserted on. |
| * @param {string} optionsHash The hash of the rewrite options. This is required |
| * to perform the property cache lookup when the beacon is handled by the |
| * sever. |
| * @param {string} nonce The nonce sent by the server. |
| * @param {Array.<string>} selectors List of the selectors on the page. |
| */ |
| pagespeed.CriticalCssBeacon = function(beaconUrl, htmlUrl, optionsHash, |
| nonce, selectors) { |
| /** |
| * We divide up the main loop of checkCssSelectors into multiple calls with |
| * window.setTimeout to minimize the delay in processing other events on the |
| * page. This constant sets the number of candidate selectors we check in a |
| * single call to checkCssSelectors. |
| * |
| * This value is choosen so that each iteration takes around ~100ms on a |
| * modern mobile phone. On a nexus 4, each document.querySelector call was |
| * measured to take around 400us on a complex page. |
| * |
| * @const |
| * @private |
| */ |
| this.MAXITERS_ = 250; |
| |
| this.beaconUrl_ = beaconUrl; |
| this.htmlUrl_ = htmlUrl; |
| this.optionsHash_ = optionsHash; |
| this.nonce_ = nonce; |
| this.selectors_ = selectors; |
| this.criticalSelectors_ = []; |
| this.idx_ = 0; |
| }; |
| |
| |
| /** |
| * Send the selectors that have been collected into the criticalSelectors_ |
| * member var back to the server. |
| * @private |
| */ |
| pagespeed.CriticalCssBeacon.prototype.sendBeacon_ = function() { |
| var data = 'oh=' + this.optionsHash_ + '&n=' + this.nonce_; |
| data += '&cs='; |
| for (var i = 0; i < this.criticalSelectors_.length; ++i) { |
| var tmp = (i > 0) ? ',' : ''; |
| tmp += encodeURIComponent(this.criticalSelectors_[i]); |
| // TODO(jud): Don't truncate the critical selectors list if we exceed |
| // MAX_DATA_LEN. Either send a signal back that we exceeded the limit, or |
| // send multiple beacons back with all the data. |
| if ((data.length + tmp.length) > pagespeedutils.MAX_POST_SIZE) { |
| break; |
| } |
| data += tmp; |
| } |
| // Export the URL for testing purposes. |
| pagespeed['criticalCssBeaconData'] = data; |
| // TODO(jud): This beacon should coordinate with the add_instrumentation JS |
| // so that only one beacon request is sent if both filters are enabled. |
| pagespeedutils.sendBeacon(this.beaconUrl_, this.htmlUrl_, data); |
| }; |
| |
| |
| /** |
| * Check if CSS selectors apply to DOM elements that are visible on initial page |
| * load. |
| * @param {Function} callback Function to call when calculating the selectors |
| * has finished. |
| * @private |
| */ |
| pagespeed.CriticalCssBeacon.prototype.checkCssSelectors_ = function(callback) { |
| for (var i = 0; i < this.MAXITERS_ && this.idx_ < this.selectors_.length; |
| ++i, ++this.idx_) { |
| try { |
| // If this selector matched any DOM elements, then consider it critical. |
| if (document.querySelector(this.selectors_[this.idx_]) != null) { |
| this.criticalSelectors_.push(this.selectors_[this.idx_]); |
| } |
| } catch (e) { |
| // SYNTAX_ERR is thrown if the browser can't parse a selector (eg, CSS3 in |
| // a CSS2.1 browser). Ignore these exceptions. |
| // TODO(jud): Consider if continue is the right thing to do here. It may |
| // be safer to mark this selector as critical if the browser didn't |
| // understand it. |
| continue; |
| } |
| } |
| |
| if (this.idx_ < this.selectors_.length) { |
| window.setTimeout(this.checkCssSelectors_.bind(this), 0, callback); |
| } else { |
| callback(); |
| } |
| }; |
| |
| |
| /** |
| * Initialize. |
| * @param {string} beaconUrl The URL on the server to send the beacon to. |
| * @param {string} htmlUrl Url of the page the beacon is being inserted on. |
| * @param {string} optionsHash The hash of the rewrite options. |
| * @param {string} nonce The nonce sent by the server. |
| * @param {Array.<string>} selectors List of the selectors on the page. |
| */ |
| pagespeed.criticalCssBeaconInit = function(beaconUrl, htmlUrl, optionsHash, |
| nonce, selectors) { |
| // Verify that the browser supports the APIs we need and bail out early if we |
| // don't. |
| if (!document.querySelector || !Function.prototype.bind) { |
| return; |
| } |
| |
| var temp = new pagespeed.CriticalCssBeacon(beaconUrl, htmlUrl, optionsHash, |
| nonce, selectors); |
| // Add event to the onload handler to scan selectors and beacon back which |
| // apply to critical elements. |
| var beacon_onload = function() { |
| // Attempt not to block other onload events on the page by wrapping in |
| // setTimeout(). |
| window.setTimeout(function() { |
| temp.checkCssSelectors_(function() { |
| temp.sendBeacon_(); |
| }); |
| }, 0); |
| }; |
| pagespeedutils.addHandler(window, 'load', beacon_onload); |
| }; |
| |
| pagespeed['criticalCssBeaconInit'] = pagespeed.criticalCssBeaconInit; |