Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 'use strict'; | |
| 6 | |
| 7 /** @suppress {duplicate} */ | |
| 8 var remoting = remoting || {}; | |
| 9 | |
| 10 /** | |
| 11 * XmppStreamParser is used to split data received over XMPP connection into | |
| 12 * individual stanzas. Data is fed to the parser using appendData() method. | |
| 13 * onStartCallback is called once for the stream header and after that | |
|
Jamie
2014/08/29 02:14:09
There is no onStartCallback.
Sergey Ulanov
2014/08/29 23:40:31
Done.
| |
| 14 * onStanzaCallback is called for each complete stanza. | |
| 15 * | |
| 16 * @param {function(Element):void} onStanzaCallback | |
| 17 * @param {function(string):void} onErrorCallback | |
| 18 * @constructor | |
| 19 */ | |
| 20 remoting.XmppStreamParser = function(onStanzaCallback, onErrorCallback) { | |
| 21 this.onStanzaCallback_ = onStanzaCallback; | |
| 22 this.onErrorCallback_ = onErrorCallback; | |
| 23 | |
| 24 | |
| 25 /** @type {Array.<ArrayBuffer>} */ | |
|
Jamie
2014/08/29 02:14:09
Please add descriptions of these members.
Sergey Ulanov
2014/08/29 23:40:31
Done.
| |
| 26 this.data_ = []; | |
| 27 this.depth_ = 0; | |
| 28 this.error_ = false; | |
| 29 this.startTag_ = ''; | |
| 30 this.startTagEnd_ = ''; | |
| 31 this.currentStanza_ = ''; | |
| 32 } | |
| 33 | |
| 34 /** @param {ArrayBuffer} data */ | |
| 35 remoting.XmppStreamParser.prototype.appendData = function(data) { | |
| 36 base.debug.assert(!this.error_); | |
| 37 | |
| 38 this.data_.push(data); | |
| 39 | |
| 40 // Check if the newly appended data completes XML tag or a piece of text by | |
| 41 // looking for '<' and '>' char codes. This has to be done before converting | |
| 42 // data to string because the input may not contain complete UTF-8 sequence. | |
| 43 var tagStartCode = '<'.charCodeAt(0); | |
| 44 var tagEndCode = '>'.charCodeAt(0); | |
| 45 var spaceCode = ' '.charCodeAt(0); | |
| 46 var tryAgain = true; | |
| 47 while (this.data_.length > 0 && tryAgain && !this.error_) { | |
| 48 tryAgain = false; | |
| 49 | |
| 50 // If we are not currently in a middle of a stanza then skip spaces (server | |
| 51 // may send spaces periodically as heartbeats) and make sure that the first | |
| 52 // character starts XML tag. | |
| 53 if (this.depth_ <= 1) { | |
|
kelvinp
2014/08/29 01:37:10
base.debug.assert(this.depth_ > 0);
Sergey Ulanov
2014/08/29 23:40:30
depth_ can be 0 here.
| |
| 54 var view = new DataView(this.data_[0]); | |
| 55 var firstChar = view.getUint8(0); | |
| 56 if (firstChar == spaceCode) { | |
| 57 tryAgain = true; | |
| 58 this.extractToken_(1); | |
|
Jamie
2014/08/29 02:14:09
This doesn't look right. It seems to be testing wh
Sergey Ulanov
2014/08/29 23:40:31
That's for catching this. I've simplified this cod
| |
| 59 continue; | |
| 60 } else if (firstChar != tagStartCode) { | |
| 61 this.processError_('Received unexpected text data: ' + | |
| 62 base.decodeUtf8(this.data_[0])); | |
|
Jamie
2014/08/29 02:14:10
Your earlier comment stated that this might be inc
Sergey Ulanov
2014/08/29 23:40:31
Good point. decodeUtf8() throws in that case. I ch
| |
| 63 return; | |
| 64 } | |
| 65 } | |
| 66 | |
| 67 var view = new DataView(this.data_[this.data_.length - 1]); | |
|
Jamie
2014/08/29 02:14:10
It's not obvious what this second loop is doing. I
Sergey Ulanov
2014/08/29 23:40:31
Done.
| |
| 68 for (var i = 0; i < view.byteLength; ++i) { | |
| 69 var currentChar = view.getUint8(i); | |
| 70 if (currentChar == tagStartCode) { | |
| 71 if (this.data_.length > 1 || i > 0) { | |
| 72 this.processText_(this.extractToken_(i)); | |
| 73 tryAgain = true; | |
| 74 break; | |
| 75 } | |
| 76 } else if (currentChar == tagEndCode) { | |
| 77 var tag = this.extractToken_(i + 1); | |
| 78 if (tag.charAt(0) != '<') { | |
| 79 this.processError_('Received \'>\' without \'<\': ' + tag); | |
| 80 return; | |
| 81 } | |
| 82 this.processTag_(tag); | |
| 83 tryAgain = true; | |
| 84 break; | |
| 85 } | |
| 86 } | |
| 87 } | |
| 88 } | |
| 89 | |
| 90 /** @param {string} text */ | |
| 91 remoting.XmppStreamParser.prototype.processText_ = function(text) { | |
| 92 // Tokenization code in appendData() shouldn't allow text tokens in between | |
| 93 // stanzas. | |
| 94 base.debug.assert(this.depth_ > 1); | |
| 95 this.currentStanza_ += text; | |
| 96 } | |
| 97 | |
| 98 /** @param {string} tag */ | |
| 99 remoting.XmppStreamParser.prototype.processTag_ = function(tag) { | |
| 100 base.debug.assert(tag.charAt(0) == '<'); | |
| 101 base.debug.assert(tag.charAt(tag.length - 1) == '>'); | |
| 102 | |
| 103 this.currentStanza_ += tag; | |
| 104 | |
| 105 var openTag = tag.charAt(1) != '/'; | |
| 106 if (openTag) { | |
| 107 ++this.depth_; | |
| 108 if (this.depth_ == 1) { | |
| 109 this.startTag_ = this.currentStanza_; | |
| 110 this.currentStanza_ = ''; | |
| 111 | |
| 112 // Create end tag matching the start. | |
| 113 var tagName = | |
| 114 this.startTag_.substr(1, this.startTag_.length - 2).split(' ', 1)[0]; | |
| 115 this.startTagEnd_ = '</' + tagName + '>'; | |
| 116 | |
| 117 // Try parsing start together with the end | |
| 118 var parsed = this.parseTag_(this.startTag_ + this.startTagEnd_); | |
| 119 if (!parsed) { | |
| 120 this.processError_('Failed to parse start tag: ' + this.startTag_); | |
| 121 return; | |
| 122 } | |
| 123 } | |
| 124 } | |
| 125 | |
| 126 var closingTag = | |
| 127 (tag.charAt(1) == '/') || (tag.charAt(tag.length - 2) == '/'); | |
| 128 if (closingTag) { | |
| 129 // The first start tag is not expected to be closed. | |
| 130 if (this.depth_ <= 1) { | |
| 131 this.processError_('Unexpected closing tag: ' + tag) | |
| 132 return; | |
| 133 } | |
| 134 --this.depth_; | |
| 135 if (this.depth_ == 1) { | |
| 136 this.processCompleteStanza_(); | |
| 137 this.currentStanza_ = ''; | |
| 138 } | |
| 139 } | |
| 140 } | |
| 141 | |
| 142 remoting.XmppStreamParser.prototype.processCompleteStanza_ = function() { | |
| 143 var stanza = this.startTag_ + this.currentStanza_ + this.startTagEnd_; | |
| 144 var parsed = this.parseTag_(stanza); | |
| 145 if (!parsed) { | |
| 146 this.processError_('Failed to parse stanza: ' + this.currentStanza_); | |
| 147 return; | |
| 148 } | |
| 149 this.onStanzaCallback_(parsed.childNodes[0]); | |
|
kelvinp
2014/08/29 01:37:10
childNodes also contains all nodes, including text
Sergey Ulanov
2014/08/29 23:40:31
Done.
| |
| 150 } | |
| 151 | |
| 152 /** @param {string} text */ | |
| 153 remoting.XmppStreamParser.prototype.processError_ = function(text) { | |
| 154 this.onErrorCallback_(text); | |
| 155 this.error_ = true; | |
| 156 } | |
| 157 | |
| 158 /** | |
| 159 * Helper that assembles block of data from pieces in this.data_ up to specified | |
| 160 * position in the last buffer and then removes that data from this.data_ . | |
| 161 * | |
| 162 * @param {number} endPos Specifies how many bytes should be taken from the last | |
| 163 * buffer in this.data_ . | |
| 164 * @returns {string} | |
| 165 */ | |
| 166 remoting.XmppStreamParser.prototype.extractToken_ = function(endPos) { | |
|
Jamie
2014/08/29 02:14:09
What is a "token" in this context? This seems to b
Sergey Ulanov
2014/08/29 23:40:31
Renamed extractStringFromBuffer_.
| |
| 167 var size = endPos; | |
| 168 for (var i = 0; i < this.data_.length - 1; ++i) { | |
| 169 size += this.data_[i].byteLength; | |
| 170 } | |
| 171 | |
| 172 var buffer = new Uint8Array(size); | |
| 173 var pos = 0; | |
| 174 for (var i = 0; i < this.data_.length - 1; ++i) { | |
| 175 buffer.set(new Uint8Array(this.data_[i]), pos); | |
| 176 pos += this.data_[i].byteLength; | |
| 177 } | |
| 178 if (endPos > 0) { | |
| 179 buffer.set(new Uint8Array(this.data_[this.data_.length - 1], 0, endPos), | |
| 180 pos); | |
| 181 pos += endPos; | |
| 182 } | |
| 183 base.debug.assert(pos == size); | |
| 184 | |
| 185 // Remove copied data from |data_|. | |
| 186 var dataLeft = this.data_[this.data_.length - 1].slice(endPos); | |
|
Jamie
2014/08/29 02:14:10
I suggest calling this dataRemaining, since it's a
Sergey Ulanov
2014/08/29 23:40:31
Done, though some people would say that arrays sta
| |
| 187 if (dataLeft.byteLength == 0) { | |
| 188 this.data_ = [] | |
| 189 } else { | |
| 190 this.data_ = [dataLeft]; | |
| 191 } | |
| 192 | |
| 193 return base.decodeUtf8(buffer.buffer); | |
| 194 } | |
| 195 | |
| 196 /** | |
| 197 * @param {string} text | |
| 198 * @return {Element} | |
| 199 */ | |
| 200 remoting.XmppStreamParser.prototype.parseTag_ = function(text) { | |
| 201 /** @type {Document} */ | |
| 202 var result = new DOMParser().parseFromString(text, 'text/xml'); | |
| 203 if (result.querySelector('parsererror') != null) | |
| 204 return null; | |
| 205 return result.firstChild; | |
| 206 } | |
| OLD | NEW |