| Index: ios/chrome/browser/ui/reader_mode/reader_mode_checker.mm
|
| diff --git a/ios/chrome/browser/ui/reader_mode/reader_mode_checker.mm b/ios/chrome/browser/ui/reader_mode/reader_mode_checker.mm
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..80432915364f782ee3d59f03e3577225de6428e8
|
| --- /dev/null
|
| +++ b/ios/chrome/browser/ui/reader_mode/reader_mode_checker.mm
|
| @@ -0,0 +1,166 @@
|
| +// Copyright 2015 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#import "ios/chrome/browser/ui/reader_mode/reader_mode_checker.h"
|
| +
|
| +#include "base/logging.h"
|
| +#include "base/strings/sys_string_conversions.h"
|
| +#include "components/dom_distiller/core/distillable_page_detector.h"
|
| +#include "components/dom_distiller/core/experiments.h"
|
| +#include "components/dom_distiller/core/page_features.h"
|
| +#include "components/dom_distiller/core/url_utils.h"
|
| +#include "components/grit/components_resources.h"
|
| +#include "ios/web/public/web_state/js/crw_js_injection_evaluator.h"
|
| +#include "ios/web/public/web_state/web_state.h"
|
| +#include "ui/base/resource/resource_bundle.h"
|
| +
|
| +ReaderModeCheckerObserver::ReaderModeCheckerObserver(
|
| + ReaderModeChecker* readerModeChecker)
|
| + : readerModeChecker_(readerModeChecker) {
|
| + DCHECK(readerModeChecker);
|
| + readerModeChecker_->AddObserver(this);
|
| +}
|
| +
|
| +ReaderModeCheckerObserver::~ReaderModeCheckerObserver() {
|
| + readerModeChecker_->RemoveObserver(this);
|
| +}
|
| +
|
| +enum class ReaderModeChecker::Status {
|
| + kSwitchNo, // There is no way a switch could happen.
|
| + kSwitchMaybe, // Not sure, it may be possible.
|
| + kSwitchPossible, // It is possible to switch to reader mode.
|
| +};
|
| +
|
| +ReaderModeChecker::ReaderModeChecker(web::WebState* web_state)
|
| + : web::WebStateObserver(web_state),
|
| + is_distillable_(Status::kSwitchNo),
|
| + weak_factory_(this) {
|
| + DCHECK(web_state);
|
| +}
|
| +
|
| +ReaderModeChecker::~ReaderModeChecker() {
|
| + for (auto& observer : observers_)
|
| + observer.ReaderModeCheckerDestroyed();
|
| +}
|
| +
|
| +bool ReaderModeChecker::CanSwitchToReaderMode() {
|
| + return is_distillable_ != ReaderModeChecker::Status::kSwitchNo;
|
| +}
|
| +
|
| +void ReaderModeChecker::PageLoaded(
|
| + web::PageLoadCompletionStatus load_completion_status) {
|
| + if (load_completion_status != web::PageLoadCompletionStatus::SUCCESS)
|
| + return;
|
| +
|
| + if (is_distillable_ == Status::kSwitchPossible)
|
| + return;
|
| +
|
| + CheckIsDistillable();
|
| +}
|
| +
|
| +void ReaderModeChecker::NavigationItemCommitted(
|
| + const web::LoadCommittedDetails& load_details) {
|
| + // When NavigationItemCommitted the page may not be completely loaded. But in
|
| + // order to get reader mode data faster this class tries to inject the
|
| + // javascript early. If it fails, it will be retried at PageLoaded().
|
| + is_distillable_ = Status::kSwitchNo;
|
| + CheckIsDistillable();
|
| +}
|
| +
|
| +void ReaderModeChecker::CheckIsDistillableOG(CRWJSInjectionReceiver* receiver) {
|
| + // Retrieve the javascript used to figure out if the page is at all
|
| + // distillable.
|
| + NSString* is_distillable_js =
|
| + base::SysUTF8ToNSString(dom_distiller::url_utils::GetIsDistillableJs());
|
| + DCHECK(is_distillable_js);
|
| +
|
| + // In case |this| gets deallocated before the block executes, the block only
|
| + // references a weak pointer.
|
| + base::WeakPtr<ReaderModeChecker> weak_this(weak_factory_.GetWeakPtr());
|
| + [receiver executeJavaScript:is_distillable_js
|
| + completionHandler:^(id result, NSError* error) {
|
| + if (!weak_this || error || !result)
|
| + return; // Inconclusive.
|
| + if ([result isEqual:@YES]) {
|
| + weak_this->is_distillable_ = Status::kSwitchPossible;
|
| + for (auto& observer : weak_this->observers_)
|
| + observer.PageIsDistillable();
|
| + } else {
|
| + weak_this->is_distillable_ = Status::kSwitchMaybe;
|
| + }
|
| + }];
|
| +}
|
| +
|
| +void ReaderModeChecker::CheckIsDistillableDetector(
|
| + CRWJSInjectionReceiver* receiver) {
|
| + // Retrieve the javascript used to figure out if the page is at all
|
| + // distillable.
|
| + NSString* extract_features_js = base::SysUTF8ToNSString(
|
| + ResourceBundle::GetSharedInstance()
|
| + .GetRawDataResource(IDR_EXTRACT_PAGE_FEATURES_JS)
|
| + .as_string());
|
| + DCHECK(extract_features_js);
|
| +
|
| + // In case |this| gets deallocated before the block executes, the block only
|
| + // references a weak pointer.
|
| + base::WeakPtr<ReaderModeChecker> weak_this(weak_factory_.GetWeakPtr());
|
| + [receiver
|
| + executeJavaScript:extract_features_js
|
| + completionHandler:^(id result, NSError* error) {
|
| + if (!weak_this || error || ![result isKindOfClass:[NSString class]]) {
|
| + return; // Inconclusive.
|
| + }
|
| + const dom_distiller::DistillablePageDetector* detector =
|
| + dom_distiller::DistillablePageDetector::GetDefault();
|
| + const base::StringValue value(base::SysNSStringToUTF8(result));
|
| + std::vector<double> features(
|
| + dom_distiller::CalculateDerivedFeaturesFromJSON(&value));
|
| + if (detector->Classify(features)) {
|
| + weak_this->is_distillable_ = Status::kSwitchPossible;
|
| + for (auto& observer : weak_this->observers_)
|
| + observer.PageIsDistillable();
|
| + } else {
|
| + weak_this->is_distillable_ = Status::kSwitchMaybe;
|
| + }
|
| + }];
|
| +}
|
| +
|
| +void ReaderModeChecker::CheckIsDistillable() {
|
| + CRWJSInjectionReceiver* receiver = web_state()->GetJSInjectionReceiver();
|
| +
|
| + if (!receiver || !web_state()->ContentIsHTML() ||
|
| + web_state()->IsShowingWebInterstitial()) {
|
| + is_distillable_ = Status::kSwitchNo;
|
| + return;
|
| + }
|
| +
|
| + switch (dom_distiller::GetDistillerHeuristicsType()) {
|
| + case dom_distiller::DistillerHeuristicsType::NONE:
|
| + is_distillable_ = Status::kSwitchMaybe;
|
| + break;
|
| + case dom_distiller::DistillerHeuristicsType::OG_ARTICLE:
|
| + CheckIsDistillableOG(receiver);
|
| + break;
|
| + case dom_distiller::DistillerHeuristicsType::ADABOOST_MODEL:
|
| + CheckIsDistillableDetector(receiver);
|
| + break;
|
| + case dom_distiller::DistillerHeuristicsType::ALWAYS_TRUE:
|
| + is_distillable_ = Status::kSwitchPossible;
|
| + break;
|
| + }
|
| +}
|
| +
|
| +void ReaderModeChecker::WebStateDestroyed() {
|
| + is_distillable_ = Status::kSwitchNo;
|
| +}
|
| +
|
| +void ReaderModeChecker::AddObserver(ReaderModeCheckerObserver* observer) {
|
| + DCHECK(!observers_.HasObserver(observer));
|
| + observers_.AddObserver(observer);
|
| +}
|
| +
|
| +void ReaderModeChecker::RemoveObserver(ReaderModeCheckerObserver* observer) {
|
| + DCHECK(observers_.HasObserver(observer));
|
| + observers_.RemoveObserver(observer);
|
| +}
|
|
|