diff options
-rw-r--r-- | net/base/filter.cc | 64 | ||||
-rw-r--r-- | net/base/gzip_filter_unittest.cc | 29 | ||||
-rw-r--r-- | net/base/sdch_filter.cc | 37 | ||||
-rw-r--r-- | net/base/sdch_filter_unittest.cc | 213 | ||||
-rw-r--r-- | net/base/sdch_manager.cc | 2 | ||||
-rw-r--r-- | net/base/sdch_manager.h | 2 |
6 files changed, 267 insertions, 80 deletions
diff --git a/net/base/filter.cc b/net/base/filter.cc index ceae763..275d398 100644 --- a/net/base/filter.cc +++ b/net/base/filter.cc @@ -117,7 +117,9 @@ void Filter::FixupEncodingTypes( } } + // If the request was for SDCH content, then we might need additional fixups. if (!filter_context.IsSdchResponse()) { + // It was not an SDCH request, so we'll just record stats. if (1 < encoding_types->size()) { // Multiple filters were intended to only be used for SDCH (thus far!) SdchManager::SdchErrorRecovery( @@ -131,7 +133,12 @@ void Filter::FixupEncodingTypes( return; } - // If content encoding included SDCH, then everything is fine. + // The request was tagged as an SDCH request, which means the server supplied + // a dictionary, and we advertised it in the request. Some proxies will do + // very strange things to the request, or the response, so we have to handle + // them gracefully. + + // If content encoding included SDCH, then everything is "relatively" fine. if (!encoding_types->empty() && (FILTER_TYPE_SDCH == encoding_types->front())) { // Some proxies (found currently in Argentina) strip the Content-Encoding @@ -147,21 +154,31 @@ void Filter::FixupEncodingTypes( return; } - // SDCH "search results" protective hack: To make sure we don't break the only - // currently deployed SDCH enabled server! Be VERY cautious about proxies that - // strip all content-encoding to not include sdch. IF we don't see content - // encodings that seem to match what we'd expect from a server that asked us - // to use a dictionary (and we advertised said dictionary in the GET), then - // we set the encoding to (try to) use SDCH to decode. Note that SDCH will - // degrade into a pass-through filter if it doesn't have a viable dictionary - // hash in its header. Also note that a solo "sdch" will implicitly create - // a "sdch,gzip" decoding filter, where the gzip portion will degrade to a - // pass through if a gzip header is not encountered. Hence we can replace - // "gzip" with "sdch" and "everything will work." - // The one failure mode comes when we advertise a dictionary, and the server - // tries to *send* a gzipped file (not gzip encode content), and then we could - // do a gzip decode :-(. Since current server support does not ever see such - // a transfer, we are safe (for now). + // There are now several cases to handle for an SDCH request. Foremost, if + // the outbound request was stripped so as not to advertise support for + // encodings, we might get back content with no encoding, or (for example) + // just gzip. We have to be sure that any changes we make allow for such + // minimal coding to work. That issue is why we use TENTATIVE filters if we + // add any, as those filters sniff the content, and act as pass-through + // filters if headers are not found. + + // If the outbound GET is not modified, then the server will generally try to + // send us SDCH encoded content. As that content returns, there are several + // corruptions of the header "content-encoding" that proxies may perform (and + // have been detected in the wild). We already dealt with the a honest + // content encoding of "sdch,gzip" being corrupted into "sdch" with on change + // of the actual content. Another common corruption is to either disscard + // the accurate content encoding, or to replace it with gzip only (again, with + // no change in actual content). The last observed corruption it to actually + // change the content, such as by re-gzipping it, and that may happen along + // with corruption of the stated content encoding (wow!). + + // The one unresolved failure mode comes when we advertise a dictionary, and + // the server tries to *send* a gzipped file (not gzip encode content), and + // then we could do a gzip decode :-(. Since SDCH is only (currently) + // supported server side on paths that only send HTML content, this mode has + // never surfaced in the wild (and is unlikely to). + // We will gather a lot of stats as we perform the fixups if (StartsWithASCII(mime_type, kTextHtml, false)) { // Suspicious case: Advertised dictionary, but server didn't use sdch, and // we're HTML tagged. @@ -193,9 +210,18 @@ void Filter::FixupEncodingTypes( } } - encoding_types->clear(); - encoding_types->push_back(FILTER_TYPE_SDCH_POSSIBLE); - encoding_types->push_back(FILTER_TYPE_GZIP_HELPING_SDCH); + // Leave the existing encoding type to be processed first, and add our + // tentative decodings to be done afterwards. Vodaphone UK reportedyl will + // perform a second layer of gzip encoding atop the server's sdch,gzip + // encoding, and then claim that the content encoding is a mere gzip. As a + // result we'll need (in that case) to do the gunzip, plus our tentative + // gunzip and tentative SDCH decoding. + // This approach nicely handles the empty() list as well, and should work with + // other (as yet undiscovered) proxies the choose to re-compressed with some + // other encoding (such as bzip2, etc.). + encoding_types->insert(encoding_types->begin(), + FILTER_TYPE_GZIP_HELPING_SDCH); + encoding_types->insert(encoding_types->begin(), FILTER_TYPE_SDCH_POSSIBLE); return; } diff --git a/net/base/gzip_filter_unittest.cc b/net/base/gzip_filter_unittest.cc index 5d1b9c5..c933014 100644 --- a/net/base/gzip_filter_unittest.cc +++ b/net/base/gzip_filter_unittest.cc @@ -269,35 +269,6 @@ TEST_F(GZipUnitTest, DecodeGZip) { EXPECT_EQ(memcmp(source_buffer(), gzip_decode_buffer, source_len()), 0); } -// SDCH scenario: decoding gzip data when content type says sdch,gzip. -// This tests that sdch will degrade to pass through, and is what allows robust -// handling when the response *might* be sdch,gzip by simply adding in the -// tentative sdch decode. -// All test code is otherwise modeled after the "basic" scenario above. -TEST_F(GZipUnitTest, DecodeGZipWithMistakenSdch) { - // Decode the compressed data with filter - std::vector<Filter::FilterType> filter_types; - filter_types.push_back(Filter::FILTER_TYPE_SDCH); - filter_types.push_back(Filter::FILTER_TYPE_GZIP); - MockFilterContext filter_context(kDefaultBufferSize); - // We need a good response code to be sure that a proxy isn't injecting an - // error page (As is done by BlueCoat proxies and described in bug 8916). - filter_context.SetResponseCode(200); - scoped_ptr<Filter> filter(Filter::Factory(filter_types, filter_context)); - ASSERT_TRUE(filter.get()); - memcpy(filter->stream_buffer()->data(), gzip_encode_buffer_, - gzip_encode_len_); - filter->FlushStreamBuffer(gzip_encode_len_); - - char gzip_decode_buffer[kDefaultBufferSize]; - int gzip_decode_size = kDefaultBufferSize; - filter->ReadData(gzip_decode_buffer, &gzip_decode_size); - - // Compare the decoding result with source data - EXPECT_TRUE(gzip_decode_size == source_len()); - EXPECT_EQ(memcmp(source_buffer(), gzip_decode_buffer, source_len()), 0); -} - // Tests we can call filter repeatedly to get all the data decoded. // To do that, we create a filter with a small buffer that can not hold all // the input data. diff --git a/net/base/sdch_filter.cc b/net/base/sdch_filter.cc index 9ddac28..8a21ed7 100644 --- a/net/base/sdch_filter.cc +++ b/net/base/sdch_filter.cc @@ -152,45 +152,50 @@ Filter::FilterStatus SdchFilter::ReadFilteredData(char* dest_buffer, DCHECK_EQ(0u, dest_buffer_excess_index_); DCHECK(dest_buffer_excess_.empty()); // This is where we try very hard to do error recovery, and make this - // protocol robust in teh face of proxies that do many different things. + // protocol robust in the face of proxies that do many different things. // If we decide that things are looking very bad (too hard to recover), // we may even issue a "meta-refresh" to reload the page without an SDCH - // advertisement (so that we are sure we're not hurting anything). First - // we try for some light weight recovery, and teh final else clause below - // supports the last ditch meta-refresh approach. + // advertisement (so that we are sure we're not hurting anything). // // Watch out for an error page inserted by the proxy as part of a 40x // error response. When we see such content molestation, we certainly // need to fall into the meta-refresh case. bool successful_response = filter_context().GetResponseCode() == 200; - if (possible_pass_through_ && successful_response) { - // This is the most graceful response. There really was no error. We - // were just overly cautious. + if (filter_context().GetResponseCode() == 404) { + // We could be more generous, but for now, only a "NOT FOUND" code will + // cause a pass through. All other codes will fall into a meta-refresh + // attempt. + SdchManager::SdchErrorRecovery(SdchManager::PASS_THROUGH_404_CODE); + decoding_status_ = PASS_THROUGH; + } else if (possible_pass_through_ && successful_response) { + // This is the potentially most graceful response. There really was no + // error. We were just overly cautious when we added a TENTATIVE_SDCH. // We added the sdch coding tag, and it should not have been added. // This can happen in server experiments, where the server decides // not to use sdch, even though there is a dictionary. To be // conservative, we locally added the tentative sdch (fearing that a // proxy stripped it!) and we must now recant (pass through). SdchManager::SdchErrorRecovery(SdchManager::DISCARD_TENTATIVE_SDCH); - decoding_status_ = PASS_THROUGH; - dest_buffer_excess_ = dictionary_hash_; // Send what we scanned. + // However.... just to be sure we don't get burned by proxies that + // re-compress with gzip or other system, we can sniff to see if this + // is compressed data etc. For now, we do nothing, which gets us into + // the meta-refresh result. + // TODO(jar): Improve robustness by sniffing for valid text that we can + // actual use re: decoding_status_ = PASS_THROUGH; } else if (successful_response && !dictionary_hash_is_plausible_) { // One of the first 9 bytes precluded consideration as a hash. // This can't be an SDCH payload, even though the server said it was. // This is a major error, as the server or proxy tagged this SDCH even // though it is not! - // The good news is that error recovery is clear... SdchManager::SdchErrorRecovery(SdchManager::PASSING_THROUGH_NON_SDCH); + // Meta-refresh won't help... we didn't advertise an SDCH dictionary!! decoding_status_ = PASS_THROUGH; + } + + if (decoding_status_ == PASS_THROUGH) { dest_buffer_excess_ = dictionary_hash_; // Send what we scanned. } else { // This is where we try to do the expensive meta-refresh. - // Either this was an error response (probably an error page inserted - // by a proxy, as in bug 8916) or else we don't have the dictionary that - // was demanded. - // With very low probability, random garbage data looked like a - // dictionary specifier (8 ASCII characters followed by a null), but - // that is sufficiently unlikely that we ignore it. if (std::string::npos == mime_type_.find("text/html")) { // Since we can't do a meta-refresh (along with an exponential // backoff), we'll just make sure this NEVER happens again. diff --git a/net/base/sdch_filter_unittest.cc b/net/base/sdch_filter_unittest.cc index abab262..02dc215 100644 --- a/net/base/sdch_filter_unittest.cc +++ b/net/base/sdch_filter_unittest.cc @@ -37,7 +37,7 @@ static const char kTestData[] = "0000000000000000000000000000000000000000000000" "000000000000000000000000000000000000000\n"; // Note SDCH compressed data will include a reference to the SDCH dictionary. -static const char kCompressedTestData[] = +static const char kSdchCompressedTestData[] = "\326\303\304\0\0\001M\0\201S\202\004\0\201E\006\001" "00000000000000000000000000000000000000000000000000000000000000000000000000" "TestData 00000000000000000000000000000000000000000000000000000000000000000" @@ -50,8 +50,8 @@ class SdchFilterTest : public testing::Test { SdchFilterTest() : test_vcdiff_dictionary_(kTestVcdiffDictionary, sizeof(kTestVcdiffDictionary) - 1), - vcdiff_compressed_data_(kCompressedTestData, - sizeof(kCompressedTestData) - 1), + vcdiff_compressed_data_(kSdchCompressedTestData, + sizeof(kSdchCompressedTestData) - 1), expanded_(kTestData, sizeof(kTestData) - 1), sdch_manager_(new SdchManager) { sdch_manager_->EnableSdchSupport(""); @@ -171,7 +171,7 @@ TEST_F(SdchFilterTest, EmptyInputOk) { TEST_F(SdchFilterTest, PassThroughWhenTentative) { std::vector<Filter::FilterType> filter_types; // Selective a tentative filter (which can fall back to pass through). - filter_types.push_back(Filter::FILTER_TYPE_SDCH_POSSIBLE); + filter_types.push_back(Filter::FILTER_TYPE_GZIP_HELPING_SDCH); const int kInputBufferSize(30); char output_buffer[20]; MockFilterContext filter_context(kInputBufferSize); @@ -181,31 +181,30 @@ TEST_F(SdchFilterTest, PassThroughWhenTentative) { filter_context.SetURL(GURL(url_string)); scoped_ptr<Filter> filter(Filter::Factory(filter_types, filter_context)); - // Supply enough data to force a pass-through mode, which means we have - // provided more than 9 characters that can't be a dictionary hash. - std::string non_sdch_content("This is not SDCH"); + // Supply enough data to force a pass-through mode.. + std::string non_gzip_content("not GZIPed data"); char* input_buffer = filter->stream_buffer()->data(); int input_buffer_size = filter->stream_buffer_size(); EXPECT_EQ(kInputBufferSize, input_buffer_size); - EXPECT_LT(static_cast<int>(non_sdch_content.size()), + EXPECT_LT(static_cast<int>(non_gzip_content.size()), input_buffer_size); - memcpy(input_buffer, non_sdch_content.data(), - non_sdch_content.size()); - filter->FlushStreamBuffer(non_sdch_content.size()); + memcpy(input_buffer, non_gzip_content.data(), + non_gzip_content.size()); + filter->FlushStreamBuffer(non_gzip_content.size()); // Try to read output. int output_bytes_or_buffer_size = sizeof(output_buffer); Filter::FilterStatus status = filter->ReadData(output_buffer, &output_bytes_or_buffer_size); - EXPECT_TRUE(non_sdch_content.size() == + EXPECT_EQ(non_gzip_content.size(), static_cast<size_t>(output_bytes_or_buffer_size)); - ASSERT_TRUE(sizeof(output_buffer) > + ASSERT_GT(sizeof(output_buffer), static_cast<size_t>(output_bytes_or_buffer_size)); output_buffer[output_bytes_or_buffer_size] = '\0'; - EXPECT_TRUE(non_sdch_content == output_buffer); + EXPECT_TRUE(non_gzip_content == output_buffer); EXPECT_EQ(Filter::FILTER_NEED_MORE_DATA, status); } @@ -933,6 +932,70 @@ TEST_F(SdchFilterTest, DefaultGzipIfSdch) { filter_context.SetMimeType("anything/mime"); filter_context.SetSdchResponse(true); Filter::FixupEncodingTypes(filter_context, &filter_types); + ASSERT_EQ(filter_types.size(), 2u); + EXPECT_EQ(filter_types[0], Filter::FILTER_TYPE_SDCH); + EXPECT_EQ(filter_types[1], Filter::FILTER_TYPE_GZIP_HELPING_SDCH); + + // First try with a large buffer (larger than test input, or compressed data). + filter_context.SetURL(url); + scoped_ptr<Filter> filter(Filter::Factory(filter_types, filter_context)); + + + // Verify that chained filter is waiting for data. + char tiny_output_buffer[10]; + int tiny_output_size = sizeof(tiny_output_buffer); + EXPECT_EQ(Filter::FILTER_NEED_MORE_DATA, + filter->ReadData(tiny_output_buffer, &tiny_output_size)); + + size_t feed_block_size = 100; + size_t output_block_size = 100; + std::string output; + EXPECT_TRUE(FilterTestData(gzip_compressed_sdch, feed_block_size, + output_block_size, filter.get(), &output)); + EXPECT_EQ(output, expanded_); + + // Next try with a tiny buffer to cover edge effects. + filter.reset(Filter::Factory(filter_types, filter_context)); + + feed_block_size = 1; + output_block_size = 1; + output.clear(); + EXPECT_TRUE(FilterTestData(gzip_compressed_sdch, feed_block_size, + output_block_size, filter.get(), &output)); + EXPECT_EQ(output, expanded_); +} + +TEST_F(SdchFilterTest, AcceptGzipSdchIfGzip) { + // Construct a valid SDCH dictionary from a VCDIFF dictionary. + const std::string kSampleDomain = "sdchtest.com"; + std::string dictionary(NewSdchDictionary(kSampleDomain)); + + std::string url_string = "http://" + kSampleDomain; + + GURL url(url_string); + EXPECT_TRUE(sdch_manager_->AddSdchDictionary(dictionary, url)); + + std::string sdch_compressed(NewSdchCompressedData(dictionary)); + + // Use Gzip to compress the sdch sdch_compressed data. + std::string gzip_compressed_sdch = gzip_compress(sdch_compressed); + + // Some proxies strip the content encoding statement down to a mere gzip, but + // pass through the original content (with full sdch,gzip encoding). + // Only claim to have gzip content, but really use the gzipped sdch content. + // System should automatically add the missing (optional) sdch. + std::vector<Filter::FilterType> filter_types; + filter_types.push_back(Filter::FILTER_TYPE_GZIP); + + const int kInputBufferSize(100); + MockFilterContext filter_context(kInputBufferSize); + filter_context.SetMimeType("anything/mime"); + filter_context.SetSdchResponse(true); + Filter::FixupEncodingTypes(filter_context, &filter_types); + ASSERT_EQ(filter_types.size(), 3u); + EXPECT_EQ(filter_types[0], Filter::FILTER_TYPE_SDCH_POSSIBLE); + EXPECT_EQ(filter_types[1], Filter::FILTER_TYPE_GZIP_HELPING_SDCH); + EXPECT_EQ(filter_types[2], Filter::FILTER_TYPE_GZIP); // First try with a large buffer (larger than test input, or compressed data). filter_context.SetURL(url); @@ -963,6 +1026,128 @@ TEST_F(SdchFilterTest, DefaultGzipIfSdch) { EXPECT_EQ(output, expanded_); } +TEST_F(SdchFilterTest, DefaultSdchGzipIfEmpty) { + // Construct a valid SDCH dictionary from a VCDIFF dictionary. + const std::string kSampleDomain = "sdchtest.com"; + std::string dictionary(NewSdchDictionary(kSampleDomain)); + + std::string url_string = "http://" + kSampleDomain; + + GURL url(url_string); + EXPECT_TRUE(sdch_manager_->AddSdchDictionary(dictionary, url)); + + std::string sdch_compressed(NewSdchCompressedData(dictionary)); + + // Use Gzip to compress the sdch sdch_compressed data. + std::string gzip_compressed_sdch = gzip_compress(sdch_compressed); + + // Only claim to have non-encoded content, but really use the gzipped sdch + // content. + // System should automatically add the missing (optional) sdch,gzip. + std::vector<Filter::FilterType> filter_types; + + const int kInputBufferSize(100); + MockFilterContext filter_context(kInputBufferSize); + filter_context.SetMimeType("anything/mime"); + filter_context.SetSdchResponse(true); + Filter::FixupEncodingTypes(filter_context, &filter_types); + ASSERT_EQ(filter_types.size(), 2u); + EXPECT_EQ(filter_types[0], Filter::FILTER_TYPE_SDCH_POSSIBLE); + EXPECT_EQ(filter_types[1], Filter::FILTER_TYPE_GZIP_HELPING_SDCH); + + // First try with a large buffer (larger than test input, or compressed data). + filter_context.SetURL(url); + scoped_ptr<Filter> filter(Filter::Factory(filter_types, filter_context)); + + + // Verify that chained filter is waiting for data. + char tiny_output_buffer[10]; + int tiny_output_size = sizeof(tiny_output_buffer); + EXPECT_EQ(Filter::FILTER_NEED_MORE_DATA, + filter->ReadData(tiny_output_buffer, &tiny_output_size)); + + size_t feed_block_size = 100; + size_t output_block_size = 100; + std::string output; + EXPECT_TRUE(FilterTestData(gzip_compressed_sdch, feed_block_size, + output_block_size, filter.get(), &output)); + EXPECT_EQ(output, expanded_); + + // Next try with a tiny buffer to cover edge effects. + filter.reset(Filter::Factory(filter_types, filter_context)); + + feed_block_size = 1; + output_block_size = 1; + output.clear(); + EXPECT_TRUE(FilterTestData(gzip_compressed_sdch, feed_block_size, + output_block_size, filter.get(), &output)); + EXPECT_EQ(output, expanded_); +} + +TEST_F(SdchFilterTest, AcceptGzipGzipSdchIfGzip) { + // Construct a valid SDCH dictionary from a VCDIFF dictionary. + const std::string kSampleDomain = "sdchtest.com"; + std::string dictionary(NewSdchDictionary(kSampleDomain)); + + std::string url_string = "http://" + kSampleDomain; + + GURL url(url_string); + EXPECT_TRUE(sdch_manager_->AddSdchDictionary(dictionary, url)); + + std::string sdch_compressed(NewSdchCompressedData(dictionary)); + + // Vodaphone (UK) Mobile Broadband provides double gzipped sdch with a content + // encoding of merely gzip (apparently, only listing the extra level of + // wrapper compression they added, but discarding the actual content encoding. + // Use Gzip to double compress the sdch sdch_compressed data. + std::string double_gzip_compressed_sdch = gzip_compress(gzip_compress( + sdch_compressed)); + + // Only claim to have gzip content, but really use the double gzipped sdch + // content. + // System should automatically add the missing (optional) sdch, gzip decoders. + std::vector<Filter::FilterType> filter_types; + filter_types.push_back(Filter::FILTER_TYPE_GZIP); + + const int kInputBufferSize(100); + MockFilterContext filter_context(kInputBufferSize); + filter_context.SetMimeType("anything/mime"); + filter_context.SetSdchResponse(true); + Filter::FixupEncodingTypes(filter_context, &filter_types); + ASSERT_EQ(filter_types.size(), 3u); + EXPECT_EQ(filter_types[0], Filter::FILTER_TYPE_SDCH_POSSIBLE); + EXPECT_EQ(filter_types[1], Filter::FILTER_TYPE_GZIP_HELPING_SDCH); + EXPECT_EQ(filter_types[2], Filter::FILTER_TYPE_GZIP); + + // First try with a large buffer (larger than test input, or compressed data). + filter_context.SetURL(url); + scoped_ptr<Filter> filter(Filter::Factory(filter_types, filter_context)); + + + // Verify that chained filter is waiting for data. + char tiny_output_buffer[10]; + int tiny_output_size = sizeof(tiny_output_buffer); + EXPECT_EQ(Filter::FILTER_NEED_MORE_DATA, + filter->ReadData(tiny_output_buffer, &tiny_output_size)); + + size_t feed_block_size = 100; + size_t output_block_size = 100; + std::string output; + EXPECT_TRUE(FilterTestData(double_gzip_compressed_sdch, feed_block_size, + output_block_size, filter.get(), &output)); + EXPECT_EQ(output, expanded_); + + // Next try with a tiny buffer to cover edge effects. + filter.reset(Filter::Factory(filter_types, filter_context)); + + feed_block_size = 1; + output_block_size = 1; + output.clear(); + EXPECT_TRUE(FilterTestData(double_gzip_compressed_sdch, feed_block_size, + output_block_size, filter.get(), &output)); + EXPECT_EQ(output, expanded_); +} + TEST_F(SdchFilterTest, DomainSupported) { GURL test_url("http://www.test.com"); GURL google_url("http://www.google.com"); diff --git a/net/base/sdch_manager.cc b/net/base/sdch_manager.cc index 7de9f0b..4dbde70 100644 --- a/net/base/sdch_manager.cc +++ b/net/base/sdch_manager.cc @@ -32,7 +32,7 @@ SdchManager* SdchManager::Global() { // static void SdchManager::SdchErrorRecovery(ProblemCodes problem) { - static LinearHistogram histogram("Sdch3.ProblemCodes_3", MIN_PROBLEM_CODE, + static LinearHistogram histogram("Sdch3.ProblemCodes_4", MIN_PROBLEM_CODE, MAX_PROBLEM_CODE - 1, MAX_PROBLEM_CODE); histogram.SetFlags(kUmaTargetedHistogramFlag); histogram.Add(problem); diff --git a/net/base/sdch_manager.h b/net/base/sdch_manager.h index a6c7a3b..c95b890 100644 --- a/net/base/sdch_manager.h +++ b/net/base/sdch_manager.h @@ -122,7 +122,7 @@ class SdchManager { CACHED_META_REFRESH_UNSUPPORTED = 75, // As above, but pulled from cache. PASSING_THROUGH_NON_SDCH = 76, // Non-html tagged as sdch but malformed. INCOMPLETE_SDCH_CONTENT = 77, // Last window was not completely decoded. - + PASS_THROUGH_404_CODE = 78, // URL not found message passing through. // Common decoded recovery methods. META_REFRESH_CACHED_RECOVERY = 80, // Probably startup tab loading. |