chrome源码之历史记录页面学习

author author     2022-08-26     270

关键词:

1.初始一定是load加载整个页面了,load方法中会调用setPageState方法来设置页面显示的内容限定信息。

// Document Functions:
/**
 * Window onload handler, sets up the page.
 */
function load() {
  uber.onContentFrameLoaded();
  FocusOutlineManager.forDocument(document);

  var searchField = $(‘search-field‘);

  historyModel = new HistoryModel();
  historyView = new HistoryView(historyModel);
  pageState = new PageState(historyModel, historyView);

  // Create default view.
  var hashData = pageState.getHashData();
  var page = parseInt(hashData.page, 10) || historyView.getPage();
  var range = /** @type {HistoryModel.Range} */(parseInt(hashData.range, 10)) ||
      historyView.getRangeInDays();
  var offset = parseInt(hashData.offset, 10) || historyView.getOffset();
  historyView.setPageState(hashData.q, page, range, offset);

  if ($(‘overlay‘)) {
    cr.ui.overlay.setupOverlay($(‘overlay‘));
    cr.ui.overlay.globalInitialization();
  }
  HistoryFocusManager.getInstance().initialize();

  var doSearch = function(e) {
    recordUmaAction(‘HistoryPage_Search‘);
    historyView.setSearch(searchField.value);

    if (isMobileVersion())
      searchField.blur();  // Dismiss the keyboard.
  };
  //yana add 161011
  var searchAllHistory = function(e){
        historyView.setPageState(‘‘, 0, HistoryModel.Range.ALL_TIME, 0);
  }
  var removeMenu = getRequiredElement(‘remove-visit‘);
  // Decorate remove-visit before disabling/hiding because the values are
  // overwritten when decorating a MenuItem that has a Command.
  cr.ui.decorate(removeMenu, MenuItem);
  removeMenu.disabled = !loadTimeData.getBoolean(‘allowDeletingHistory‘);
  removeMenu.hidden = loadTimeData.getBoolean(‘hideDeleteVisitUI‘);

  document.addEventListener(‘command‘, handleCommand);
  $(‘all-history‘).addEventListener(‘click‘, searchAllHistory);//yana add 161011
  //console.log("$(‘all-history‘)="+$(‘all-history‘));
  searchField.addEventListener(‘search‘, doSearch);
  $(‘search-button‘).addEventListener(‘click‘, doSearch);

  $(‘more-from-site‘).addEventListener(‘activate‘, function(e) {
    activeVisit.showMoreFromSite_();
    activeVisit = null;
  });

  // Only show the controls if the command line switch is activated or the user
  // is supervised.
  if (loadTimeData.getBoolean(‘groupByDomain‘)) {
    $(‘history-page‘).classList.add(‘big-topbar-page‘);
    $(‘filter-controls‘).hidden = false;
  }
  // Hide the top container which has the "Clear browsing data" and "Remove
  // selected entries" buttons if deleting history is not allowed.
  if (!loadTimeData.getBoolean(‘allowDeletingHistory‘))
    $(‘top-container‘).hidden = true;

  uber.setTitle(loadTimeData.getString(‘title‘));

  // Adjust the position of the notification bar when the window size changes.
  window.addEventListener(‘resize‘,
      historyView.positionNotificationBar.bind(historyView));

  if (isMobileVersion()) {
    // Move the search box out of the header.
    var resultsDisplay = $(‘results-display‘);
    resultsDisplay.parentNode.insertBefore($(‘search-field‘), resultsDisplay);

    window.addEventListener(
        ‘resize‘, historyView.updateClearBrowsingDataButton_);

<if expr="is_ios">
    // Trigger window resize event when search field is focused to force update
    // of the clear browsing button, which should disappear when search field
    // is active. The window is not resized when the virtual keyboard is shown
    // on iOS.
    searchField.addEventListener(‘focus‘, function() {
      cr.dispatchSimpleEvent(window, ‘resize‘);
    });
</if>  /* is_ios */

    // When the search field loses focus, add a delay before updating the
    // visibility, otherwise the button will flash on the screen before the
    // keyboard animates away.
    searchField.addEventListener(‘blur‘, function() {
      setTimeout(historyView.updateClearBrowsingDataButton_, 250);
    });

    // Move the button to the bottom of the page.
    $(‘history-page‘).appendChild($(‘clear-browsing-data‘));
  } else {
    window.addEventListener(‘message‘, function(e) {
      e = /** @type {!MessageEvent<!{method: string}>} */(e);
      if (e.data.method == ‘frameSelected‘)
        searchField.focus();
    });
    searchField.focus();
  }
    historyModel.queryDateList_();//yana add 160909

<if expr="is_ios">
  function checkKeyboardVisibility() {
    // Figure out the real height based on the orientation, becauase
    // screen.width and screen.height don‘t update after rotation.
    var screenHeight = window.orientation % 180 ? screen.width : screen.height;

    // Assume that the keyboard is visible if more than 30% of the screen is
    // taken up by window chrome.
    var isKeyboardVisible = (window.innerHeight / screenHeight) < 0.7;

    document.body.classList.toggle(‘ios-keyboard-visible‘, isKeyboardVisible);
  }
  window.addEventListener(‘orientationchange‘, checkKeyboardVisibility);
  window.addEventListener(‘resize‘, checkKeyboardVisibility);
</if> /* is_ios */
}

2.setPageState方法的实现如下,它是最基础的一个方法,其他如setSearch/setOffset/setPage/setRangeInDays实际都是调用的这个方法。

/**
 * Sets all the parameters for the history page and then reloads the view to
 * update the results.
 * @param {string} searchText The search string to set.
 * @param {number} page The page to be viewed.
 * @param {HistoryModel.Range} range The range to view or search over.
 * @param {number} offset Set the begining of the query to the specific offset.
 */
HistoryView.prototype.setPageState = function(searchText, page, range, offset) {
  this.clear_();
  this.model_.searchText_ = searchText;
  this.pageIndex_ = page;
  this.model_.requestedPage_ = page;
  this.model_.rangeInDays_ = range;
  this.model_.groupByDomain_ = false;
  if (range != HistoryModel.Range.ALL_TIME&&range != HistoryModel.Range.DAY)//yana add 160908
    this.model_.groupByDomain_ = true;
  this.model_.offset_ = offset;
  this.reload();
  pageState.setUIState(this.model_.getSearchText(),
                       this.pageIndex_,
                       this.getRangeInDays(),
                       this.getOffset());
};

3.reload:方法根据以上设置的限定条件,进行历史记录查询。

/**
 * Reload our model with the current parameters.
 */
HistoryModel.prototype.reload = function() {
  // Save user-visible state, clear the model, and restore the state.
  var search = this.searchText_;
  var page = this.requestedPage_;
  var range = this.rangeInDays_;
  var offset = this.offset_;
  var groupByDomain = this.groupByDomain_;

  this.clearModel_();
  this.searchText_ = search;
  this.requestedPage_ = page;
  this.rangeInDays_ = range;
  this.offset_ = offset;
  this.groupByDomain_ = groupByDomain;
  this.queryHistory_();
};

 

4.在上层js中通过chrome.send()来向底层发送事件请求和相关参数,其中 ‘queryHistory‘为信号名称,[this.searchText_, this.offset_, this.rangeInDays_, endTime, maxResults]为向底层传递的参数:

/**
 * Query for history, either for a search or time-based browsing.
 * @private
 */
HistoryModel.prototype.queryHistory_ = function() {
  var maxResults =
      (this.rangeInDays_ == HistoryModel.Range.ALL_TIME) ? RESULTS_PER_PAGE : 0;

  // If there are already some visits, pick up the previous query where it
  // left off.
  var lastVisit = this.visits_.slice(-1)[0];
  var endTime = lastVisit ? lastVisit.date.getTime() : 0;

  $(‘loading-spinner‘).hidden = false;
  this.inFlight_ = true;

  //yana add 20161128
    if (typeof this.timer_ != ‘undefined‘ && this.timer_) {
    clearInterval(this.timer_);
    }

  // TODO(glen): Replace this with a bound method so we don‘t need
  //     public model and view.
  this.timer_ = window.setInterval(function() {
        if($(‘loading-spinner‘).hidden==false){
          chrome.send(‘queryHistory‘,
              [this.searchText_, this.offset_, this.rangeInDays_, endTime, maxResults]);
        }
  }.bind(this), 50);
};

 

5.在底层Browsing_history_handler.cc中通过RegisterMessages函数对上层发来的事件进行响应处理:

void BrowsingHistoryHandler::RegisterMessages() {
……
……

  web_ui()->RegisterMessageCallback("queryHistory",
      base::Bind(&BrowsingHistoryHandler::HandleQueryHistory,
                 base::Unretained(this)));

……
}

之后会在BrowsingHistoryHandler::HandleQueryHistory()函数中处理查询历史记录的事件响应。

 

6.底层开始按照上层传来的要求从服务器上获取满足条件的历史记录等相关信息。

void BrowsingHistoryHandler::WebHistoryQueryComplete(
    const base::string16& search_text,
    const history::QueryOptions& options,
    base::TimeTicks start_time,
    history::WebHistoryService::Request* request,
    const base::DictionaryValue* results_value) {
  base::TimeDelta delta = base::TimeTicks::Now() - start_time;
  UMA_HISTOGRAM_TIMES("WebHistory.ResponseTime", delta);

  // If the response came in too late, do nothing.
  // TODO(dubroy): Maybe show a banner, and prompt the user to reload?
  if (!web_history_timer_.IsRunning())
    return;
  web_history_timer_.Stop();

  UMA_HISTOGRAM_ENUMERATION(
      "WebHistory.QueryCompletion",
      results_value ? WEB_HISTORY_QUERY_SUCCEEDED : WEB_HISTORY_QUERY_FAILED,
      NUM_WEB_HISTORY_QUERY_BUCKETS);

  DCHECK_EQ(0U, web_history_query_results_.size());
  const base::ListValue* events = NULL;
  if (results_value && results_value->GetList("event", &events)) {
    web_history_query_results_.reserve(events->GetSize());
    for (unsigned int i = 0; i < events->GetSize(); ++i) {
      const base::DictionaryValue* event = NULL;
      const base::DictionaryValue* result = NULL;
      const base::ListValue* results = NULL;
      const base::ListValue* ids = NULL;
      base::string16 url;
      base::string16 title;
      base::Time visit_time;

      if (!(events->GetDictionary(i, &event) &&
          event->GetList("result", &results) &&
          results->GetDictionary(0, &result) &&
          result->GetString("url", &url) &&
          result->GetList("id", &ids) &&
          ids->GetSize() > 0)) {
        LOG(WARNING) << "Improperly formed JSON response from history server.";
        continue;
      }

      // Ignore any URLs that should not be shown in the history page.
      GURL gurl(url);
      if (!CanAddURLToHistory(gurl))
        continue;

      // Title is optional, so the return value is ignored here.
      result->GetString("title", &title);

      // Extract the timestamps of all the visits to this URL.
      // They are referred to as "IDs" by the server.
      for (int j = 0; j < static_cast<int>(ids->GetSize()); ++j) {
        const base::DictionaryValue* id = NULL;
        std::string timestamp_string;
        int64_t timestamp_usec = 0;

        if (!ids->GetDictionary(j, &id) ||
            !id->GetString("timestamp_usec", &timestamp_string) ||
            !base::StringToInt64(timestamp_string, &timestamp_usec)) {
          NOTREACHED() << "Unable to extract timestamp.";
          continue;
        }
        // The timestamp on the server is a Unix time.
        base::Time time = base::Time::UnixEpoch() +
            base::TimeDelta::FromMicroseconds(timestamp_usec);

        // Get the ID of the client that this visit came from.
        std::string client_id;
        id->GetString("client_id", &client_id);

        web_history_query_results_.push_back(
            HistoryEntry(
                HistoryEntry::REMOTE_ENTRY,
                gurl,
                title,
                time,
                client_id,
                !search_text.empty(),
                base::string16(),
                /* blocked_visit */ false));
      }
    }
  }
  has_synced_results_ = results_value != nullptr;
  results_info_value_.SetBoolean("hasSyncedResults", has_synced_results_);
  if (!query_task_tracker_.HasTrackedTasks())
    ReturnResultsToFrontEnd();
}


7.将获取到的数据按照上层的需求转换为目标格式,再通过CallJavascriptFunction()调用上层js的方法并将需要返回的数据一并返回。

void BrowsingHistoryHandler::ReturnResultsToFrontEnd() {
  Profile* profile = Profile::FromWebUI(web_ui());
  BookmarkModel* bookmark_model =
      BookmarkModelFactory::GetForBrowserContext(profile);
  SupervisedUserService* supervised_user_service = NULL;
#if defined(ENABLE_SUPERVISED_USERS)
  if (profile->IsSupervised())
    supervised_user_service =
        SupervisedUserServiceFactory::GetForProfile(profile);
#endif
  browser_sync::ProfileSyncService* sync_service =
      ProfileSyncServiceFactory::GetInstance()->GetForProfile(profile);

  // Combine the local and remote results into |query_results_|, and remove
  // any duplicates.
  if (!web_history_query_results_.empty()) {
    int local_result_count = query_results_.size();
    query_results_.insert(query_results_.end(),
                          web_history_query_results_.begin(),
                          web_history_query_results_.end());
    MergeDuplicateResults(&query_results_);

    if (local_result_count) {
      // In the best case, we expect that all local results are duplicated on
      // the server. Keep track of how many are missing.
      int missing_count = std::count_if(
          query_results_.begin(), query_results_.end(), IsLocalOnlyResult);
      UMA_HISTOGRAM_PERCENTAGE("WebHistory.LocalResultMissingOnServer",
                               missing_count * 100.0 / local_result_count);
    }
  }

  bool is_md = false;
#if !defined(OS_ANDROID)
  is_md = MdHistoryUI::IsEnabled(profile);
#endif

  // Convert the result vector into a ListValue.
  base::ListValue results_value;
  for (std::vector<BrowsingHistoryHandler::HistoryEntry>::iterator it =
           query_results_.begin(); it != query_results_.end(); ++it) {
    std::unique_ptr<base::Value> value(it->ToValue(
        bookmark_model, supervised_user_service, sync_service, is_md));
    results_value.Append(std::move(value));
  }

  web_ui()->CallJavascriptFunctionUnsafe("historyResult", results_info_value_, results_value);// here is the two parameters to send to front end.
  web_ui()->CallJavascriptFunctionUnsafe(
      "showNotification", base::FundamentalValue(has_synced_results_),
      base::FundamentalValue(has_other_forms_of_browsing_history_));
  results_info_value_.Clear();
  query_results_.clear();
  web_history_query_results_.clear();
}

其中:

  // The info value that is returned to the front end with the query results.
  base::DictionaryValue results_info_value_;

  // The list of query results received from the history service.
  std::vector<HistoryEntry> query_results_;

  // The list of query results received from the history server.
  std::vector<HistoryEntry> web_history_query_results_;

 注意需要将vector类型转换成ListValue类型才能传给上层,因为CallJavascriptFunctionUnsafe函数只能接受Value类型的参数,而value类型包括如下类型:

class BASE_EXPORT Value {
 public:
  enum Type {
    TYPE_NULL = 0,
    TYPE_BOOLEAN,
    TYPE_INTEGER,
    TYPE_DOUBLE,
    TYPE_STRING,
    TYPE_BINARY,
    TYPE_DICTIONARY,
    TYPE_LIST
    // Note: Do not add more types. See the file-level comment above for why.
  };
 …… }

另外,通过CallJavascriptFunctionUnsafe方法向上层传递的参数类型的对应关系如下:

C++中的DictionaryValue类型<——>JS中的JSON类型

C++中的ListValue类型<——>JS中的ARRAY类型

以上是由chrome的机制实现(CallJavascriptFunctionUnsafe函数内部完成相应转换),DictionaryValue和ListValue类型都是chrome内部封装的类型。
综上:最终返回给上层的数据即是一个装满json类型数据的的数组。

 

8.回到上层,底层已经将获得的历史记录数据传给了上一步中指定的方法——historyResult(),即historyResult将对查询到的历史记录继续做相关处理。

以上完成一个完整的通信:

/**
 * Our history system calls this function with results from searches.
 * @param {HistoryQuery} info An object containing information about the query.
 * @param {Array<HistoryEntry>} results A list of results.
 */
function historyResult(info, results) { historyModel.addResults(info, results); }

6.接下来上层需要动态的将这些历史记录组织到DOM文档中去,并完成整个布局和显示。

几个关键的类

// Visit:
/**
 * Class to hold all the information about an entry in our model.
 * @param {HistoryEntry} result An object containing the visit‘s data.
 * @param {boolean} continued Whether this visit is on the same day as the
 *     visit before it.
 * @param {HistoryModel} model The model object this entry belongs to.
 * @constructor
 */
function Visit(result, continued, model) {
  this.model_ = model;
  this.title_ = result.title;
  this.url_ = result.url;
  this.domain_ = result.domain;
  this.starred_ = result.starred;
  this.fallbackFaviconText_ = result.fallbackFaviconText;

  // These identify the name and type of the device on which this visit
  // occurred. They will be empty if the visit occurred on the current device.
  this.deviceName = result.deviceName;
  this.deviceType = result.deviceType;

  // The ID will be set according to when the visit was displayed, not
  // received. Set to -1 to show that it has not been set yet.
  this.id_ = -1;

  this.isRendered = false;  // Has the visit already been rendered on the page?

  // All the date information is public so that owners can compare properties of
  // two items easily.

  this.date = new Date(result.time);

  // See comment in BrowsingHistoryHandler::QueryComplete - we won‘t always
  // get all of these.
  this.dateRelativeDay = result.dateRelativeDay;
  this.dateShort = result.dateShort;
  //this.dateTimeOfDay = result.dateTimeOfDay;
  //yana modify 160925
  var dayFlag = result.dateTimeOfDay.substr(0,2);
  if(dayFlag == "上午"){
    var h= result.dateTimeOfDay.substr(2).split(":")[0];
    if(h==12)    h = parseInt(h)-12;
    this.dateTimeOfDay = h+ ":" +result.dateTimeOfDay.substr(2).split(":")[1];
  }else{
    var h= result.dateTimeOfDay.substr(2).split(":")[0];
    if(h!=12)    h = parseInt(h)+12;
    this.dateTimeOfDay = h+ ":" +result.dateTimeOfDay.substr(2).split(":")[1];
  }
  // Shows the filtering behavior for that host (only used for supervised
  // users).
  // A value of |SupervisedUserFilteringBehavior.ALLOW| is not displayed so it
  // is used as the default value.
  this.hostFilteringBehavior = SupervisedUserFilteringBehavior.ALLOW;
  if (result.hostFilteringBehavior)
    this.hostFilteringBehavior = result.hostFilteringBehavior;

  this.blockedVisit = result.blockedVisit;

  // Whether this is the continuation of a previous day.
  this.continued = continued;

  this.allTimestamps = result.allTimestamps;
}
// HistoryModel:

/**
 * Global container for history data. Future optimizations might include
 * allowing the creation of a HistoryModel for each search string, allowing
 * quick flips back and forth between results.
 *
 * The history model is based around pages, and only fetching the data to
 * fill the currently requested page. This is somewhat dependent on the view,
 * and so future work may wish to change history model to operate on
 * timeframe (day or week) based containers.
 *
 * @constructor
 */
function HistoryModel() {
  this.clearModel_();
}
// HistoryView:
/**
 * Functions and state for populating the page with HTML. This should one-day
 * contain the view and use event handlers, rather than pushing HTML out and
 * getting called externally.
 * @param {HistoryModel} model The model backing this view.
 * @constructor
 */
function HistoryView(model) {
  this.editButtonTd_ = $(‘edit-button‘);
  this.editingControlsDiv_ = $(‘editing-controls‘);
  this.resultDiv_ = $(‘results-display‘);
  this.focusGrid_ = new cr.ui.FocusGrid();
  this.pageDiv_ = $(‘results-pagination‘);
  this.model_ = model;
  this.pageIndex_ = 0;
  this.lastDisplayed_ = [];
  this.hasRenderedResults_ = false;

  this.model_.setView(this);

  this.currentVisits_ = [];

  // If there is no search button, use the search button label as placeholder
  // text in the search field.
  if ($(‘search-button‘).offsetWidth == 0)
    $(‘search-field‘).placeholder = $(‘search-button‘).value;

  var self = this;

  $(‘clear-browsing-data‘).addEventListener(‘click‘, openClearBrowsingData);
  $(‘remove-selected‘).addEventListener(‘click‘, removeItems);

  // Add handlers for the page navigation buttons at the bottom.
  $(‘newest-button‘).addEventListener(‘click‘, function() {
    recordUmaAction(‘HistoryPage_NewestHistoryClick‘);
    self.setPage(0);
  });
  $(‘newer-button‘).addEventListener(‘click‘, function() {
    recordUmaAction(‘HistoryPage_NewerHistoryClick‘);
    self.setPage(self.pageIndex_ - 1);
  });
  $(‘older-button‘).addEventListener(‘click‘, function() {
    recordUmaAction(‘HistoryPage_OlderHistoryClick‘);
    self.setPage(self.pageIndex_ + 1);
  });

  $(‘timeframe-controls‘).onchange = function(e) {
    var value = parseInt(e.target.value, 10);
    self.setRangeInDays(/** @type {HistoryModel.Range<number>} */(value));
  };

  $(‘range-previous‘).addEventListener(‘click‘, function(e) {
    if (self.getRangeInDays() == HistoryModel.Range.ALL_TIME)
      self.setPage(self.pageIndex_ + 1);
    else
      self.setOffset(self.getOffset() + 1);
  });
  $(‘range-next‘).addEventListener(‘click‘, function(e) {
    if (self.getRangeInDays() == HistoryModel.Range.ALL_TIME)
      self.setPage(self.pageIndex_ - 1);
    else
      self.setOffset(self.getOffset() - 1);
  });
  $(‘range-today‘).addEventListener(‘click‘, function(e) {
    if (self.getRangeInDays() == HistoryModel.Range.ALL_TIME)
      self.setPage(0);
    else
      self.setOffset(0);
  });
}

 



关键函数:

(1)获取到每天的记录之后创建dom元素将其显示出来:

/**
 * Adds the results for a certain day. This includes a title with the day of
 * the results and the results themselves, grouped or not.
 * @param {Array} visits Visits returned by the query.
 * @param {Node} parentNode Node to which to add the results to.
 * @private
 */
HistoryView.prototype.addDayResults_ = function(visits, parentNode) {
  if (visits.length == 0)
    return;

  var firstVisit = visits[0];
  var day = parentNode.appendChild(createElementWithClassName(‘h3‘, ‘day‘));
  day.appendChild(document.createTextNode(firstVisit.dateRelativeDay));
  if (firstVisit.continued) {
    day.appendChild(document.createTextNode(‘ ‘ +
                                            loadTimeData.getString(‘cont‘)));
  }
  var dayResults = /** @type {HTMLElement} */(parentNode.appendChild(
      createElementWithClassName(‘ol‘, ‘day-results‘)));

  // Don‘t add checkboxes if entries can not be edited.
  if (!this.model_.editingEntriesAllowed)
    dayResults.classList.add(‘no-checkboxes‘);

  if (this.model_.getGroupByDomain()) {
    this.groupVisitsByDomain_(visits, dayResults);
  } else {
    var lastTime;

    for (var i = 0, visit; visit = visits[i]; i++) {
      // If enough time has passed between visits, indicate a gap in browsing.
      var thisTime = visit.date.getTime();
      //delete the gap yana 160926
      /*if (lastTime && lastTime - thisTime > BROWSING_GAP_TIME)
        dayResults.appendChild(createElementWithClassName(‘li‘, ‘gap‘));*/

      // Insert the visit into the DOM.
      dayResults.appendChild(visit.getResultDOM({ addTitleFavicon: true }));
      this.setVisitRendered_(visit);

      lastTime = thisTime;
    }
  }
};

(2)具体的显示每一天内的历史记录条目

/**
 * Returns a dom structure for a browse page result or a search page result.
 * @param {Object} propertyBag A bag of configuration properties, false by
 * default:
 *  - isSearchResult: Whether or not the result is a search result.
 *  - addTitleFavicon: Whether or not the favicon should be added.
 *  - useMonthDate: Whether or not the full date should be inserted (used for
 * monthly view).
 * @return {Node} A DOM node to represent the history entry or search result.
 */
Visit.prototype.getResultDOM = function(propertyBag) {
  var isSearchResult = propertyBag.isSearchResult || false;
  var addTitleFavicon = propertyBag.addTitleFavicon || false;
  var useMonthDate = propertyBag.useMonthDate || false;
  var focusless = propertyBag.focusless || false;
  var node = createElementWithClassName(‘li‘, ‘entry‘);
  var time = createElementWithClassName(‘span‘, ‘time‘);
  var entryBox = createElementWithClassName(‘div‘, ‘entry-box‘);
  var domain = createElementWithClassName(‘div‘, ‘domain‘);

  this.id_ = this.model_.getNextVisitId();
  var self = this;

  // Only create the checkbox if it can be used to delete an entry.
  if (this.model_.editingEntriesAllowed) {
    var checkbox = document.createElement(‘input‘);
    checkbox.type = ‘checkbox‘;
    checkbox.id = ‘checkbox-‘ + this.id_;
    checkbox.time = this.date.getTime();
    checkbox.setAttribute(‘aria-label‘, loadTimeData.getStringF(
        ‘entrySummary‘,
        this.dateTimeOfDay,
        this.starred_ ? loadTimeData.getString(‘bookmarked‘) : ‘‘,
        this.title_,
        this.domain_));
    checkbox.addEventListener(‘click‘, checkboxClicked);
    entryBox.appendChild(checkbox);

    if (focusless)
      checkbox.tabIndex = -1;

    if (!isMobileVersion()) {
      // Clicking anywhere in the entryBox will check/uncheck the checkbox.
      entryBox.setAttribute(‘for‘, checkbox.id);
      entryBox.addEventListener(‘mousedown‘, this.handleMousedown_.bind(this));
      entryBox.addEventListener(‘click‘, entryBoxClick);
      entryBox.addEventListener(‘keydown‘, this.handleKeydown_.bind(this));
    }
  }

  // Keep track of the drop down that triggered the menu, so we know
  // which element to apply the command to.
  // TODO(dubroy): Ideally we‘d use ‘activate‘, but MenuButton swallows it.
  var setActiveVisit = function(e) {
    activeVisit = self;
    var menu = $(‘action-menu‘);
    menu.dataset.devicename = self.deviceName;
    menu.dataset.devicetype = self.deviceType;
  };
  domain.textContent = this.domain_;

  entryBox.appendChild(time);

  var bookmarkSection = createElementWithClassName(
      ‘button‘, ‘bookmark-section custom-appearance‘);
  
    /*yana add 161020*/
    if (this.starred_) {
        bookmarkSection.title = loadTimeData.getString(‘removeBookmark‘);
        bookmarkSection.classList.add(‘starred‘);
    }else{
        bookmarkSection.title = loadTimeData.getString(‘addBookmark‘);
    }
    bookmarkSection.addEventListener(‘click‘, function f(e) {
      recordUmaAction(‘HistoryPage_BookmarkStarClicked‘);
      var hasClassStarred = bookmarkSection.getAttribute("class").indexOf("starred");
      if(hasClassStarred>0){
          bookmarkSection.title = loadTimeData.getString(‘addBookmark‘);
          bookmarkSection.classList.remove(‘starred‘);
          chrome.send(‘removeBookmark‘, [self.url_]);
          //this.model_.getView().onBeforeUnstarred(this);
          //this.model_.getView().onAfterUnstarred(this);
          //bookmarkSection.removeEventListener(‘click‘, f);
      }else{
          bookmarkSection.title = loadTimeData.getString(‘removeBookmark‘);
          
          bookmarkSection.classList.add(‘starred‘);
          chrome.send(‘addBookmark‘, [self.url_, self.title_]);
      }
      e.preventDefault();
    }.bind(this));


  if (focusless)
    bookmarkSection.tabIndex = -1;

  entryBox.appendChild(bookmarkSection);

  if (addTitleFavicon || this.blockedVisit) {
    var faviconSection = createElementWithClassName(‘div‘, ‘favicon‘);
    if (this.blockedVisit)
      faviconSection.classList.add(‘blocked-icon‘);
    else
      this.loadFavicon_(faviconSection);
    entryBox.appendChild(faviconSection);
  }

  var visitEntryWrapper = /** @type {HTMLElement} */(
      entryBox.appendChild(document.createElement(‘div‘)));
  if (addTitleFavicon || this.blockedVisit)
    visitEntryWrapper.classList.add(‘visit-entry‘);
  if (this.blockedVisit) {
    visitEntryWrapper.classList.add(‘blocked-indicator‘);
    visitEntryWrapper.appendChild(this.getVisitAttemptDOM_());
  } else {
    var title = visitEntryWrapper.appendChild(
        this.getTitleDOM_(isSearchResult));

    if (focusless)
      title.querySelector(‘a‘).tabIndex = -1;

    visitEntryWrapper.appendChild(domain);
  }

  if (isMobileVersion()) {
    if (this.model_.editingEntriesAllowed) {
      var removeButton = createElementWithClassName(‘button‘, ‘remove-entry‘);
      removeButton.setAttribute(‘aria-label‘,
                                loadTimeData.getString(‘removeFromHistory‘));
      removeButton.classList.add(‘custom-appearance‘);
      removeButton.addEventListener(
          ‘click‘, this.removeEntryFromHistory_.bind(this));
      entryBox.appendChild(removeButton);

      // Support clicking anywhere inside the entry box.
      entryBox.addEventListener(‘click‘, function(e) {
        if (!e.defaultPrevented) {
          self.titleLink.focus();
          self.titleLink.click();
        }
      });
    }
  } else {
    /*yana mask 160824*/
      /*
    var dropDown = createElementWithClassName(‘button‘, ‘drop-down‘);
    dropDown.value = ‘Open action menu‘;
    dropDown.title = loadTimeData.getString(‘actionMenuDescription‘);
    dropDown.setAttribute(‘menu‘, ‘#action-menu‘);
    dropDown.setAttribute(‘aria-haspopup‘, ‘true‘);

    if (focusless)
      dropDown.tabIndex = -1;

    cr.ui.decorate(dropDown, cr.ui.MenuButton);
    dropDown.respondToArrowKeys = false;

    dropDown.addEventListener(‘mousedown‘, setActiveVisit);
    dropDown.addEventListener(‘focus‘, setActiveVisit);

    // Prevent clicks on the drop down from affecting the checkbox.  We need to
    // call blur() explicitly because preventDefault() cancels any focus
    // handling.
    dropDown.addEventListener(‘click‘, function(e) {
      e.preventDefault();
      document.activeElement.blur();
    });
    entryBox.appendChild(dropDown);*/
    
    /*yana add 160825 begin*/
    var removeVisit = createElementWithClassName(‘button‘, ‘remove-visit‘);
    removeVisit.value = ‘remove from history‘;
    //removeVisit.id = ‘remove-visit‘;
    removeVisit.title = loadTimeData.getString(‘removeFromVisit‘);
    if (focusless)
      removeVisit.tabIndex = -1;
    cr.ui.decorate(removeVisit, cr.ui.MenuButton);
    //removeVisit.respondToArrowKeys = false;
    removeVisit.addEventListener(‘mousedown‘, setActiveVisit);
    removeVisit.addEventListener(‘focus‘, setActiveVisit);

    // Prevent clicks on the drop down from affecting the checkbox.  We need to
    // call blur() explicitly because preventDefault() cancels any focus
    // handling.
    removeVisit.addEventListener(‘click‘, function(e) {
      assert(!$(‘remove-visit‘).disabled);
      activeVisit.removeEntryFromHistory_(e);
      e.preventDefault();
      document.activeElement.blur();
    });
    entryBox.appendChild(removeVisit);

    var moreFromSite = createElementWithClassName(‘button‘, ‘more-from-site‘);
    moreFromSite.value = ‘more from site‘;
    //moreFromSite.id = ‘more-from-site‘;
    moreFromSite.title = loadTimeData.getString(‘searchMoreAboutThisSite‘);
    if (focusless)
      moreFromSite.tabIndex = -1;
    cr.ui.decorate(moreFromSite, cr.ui.MenuButton);
    //removeVisit.respondToArrowKeys = false;
    moreFromSite.addEventListener(‘mousedown‘, setActiveVisit);
    moreFromSite.addEventListener(‘focus‘, setActiveVisit);

    // Prevent clicks on the drop down from affecting the checkbox.  We need to
    // call blur() explicitly because preventDefault() cancels any focus
    // handling.
    moreFromSite.addEventListener(‘click‘, function(e) {
      activeVisit.showMoreFromSite_();
      activeVisit = null;
      e.preventDefault();
      document.activeElement.blur();
    });
    entryBox.appendChild(moreFromSite);
    /*yana add 160825 end*/
  }

  // Let the entryBox be styled appropriately when it contains keyboard focus.
  entryBox.addEventListener(‘focus‘, function() {
    this.classList.add(‘contains-focus‘);
  }, true);
  entryBox.addEventListener(‘blur‘, function() {
    this.classList.remove(‘contains-focus‘);
  }, true);

  var entryBoxContainer =
      createElementWithClassName(‘div‘, ‘entry-box-container‘);
  node.appendChild(entryBoxContainer);
  entryBoxContainer.appendChild(entryBox);
  //yana add 161216
  /*entryBoxContainer.oncontextmenu = function(){
        return false;
  }*/
  entryBoxContainer.addEventListener(‘contextmenu‘, function(event) {

    var selectionText = window.getSelection().toString();  
      event.preventDefault();
      var e = event||window.event;
    recordUmaAction(‘HistoryPage_EntryLinkRightClick‘);
    chrome.send("addContextMenu",[self.url_, self.title_, e.clientX, e.clientY, selectionText]);
  });


  if (isSearchResult || useMonthDate) {
    // Show the day instead of the time.
    time.appendChild(document.createTextNode(this.dateShort));
  } else {
    time.appendChild(document.createTextNode(this.dateTimeOfDay));
  }
  this.domNode_ = node;
  node.visit = this;

  return node;
};

技术分享

 












bootstrap之metronic模板的学习之路-源码分析之body部分

...部分又包含了SIDEBAR、CONTENT、QUICKSIDEBAR几个部分。body部分源码折叠后截图如下:Header页面顶部Headercontainsoflogoandtopmenubaranditusedinallpages.页面顶部(或头部)应用于所有的页面,包含logo、顶部菜单 查看详情

前端小白之每天学习记录----angula2--

1.1 Angular的发展历史 1.1.1angular起源GetAngular====>byMiskoHeveryandAdamAbrons开发效率高AnguarJS1.1.2迭代之路12年6月AngularJS1===》双向绑定、依赖注入、指令AngularJS1.3.x====》推出单次绑定语法放弃ie8浏览器支持AngularJS1.5.x=====》增加了... 查看详情

chrome神器vimium快捷键学习记录

Vimium使用快捷键总结:j:向下移动。k:向上移动。(不明白默认的<c-y>表示是啥用法,使用了c-y这三个键没有效果)h:向左移动。l:向右移动。zH:一直移动到左部。zL:一直移动到右部。gg:跳转到页面的顶部。G:跳转到页... 查看详情

程序员修炼之道学习记录之注重实效的哲学

我的源码让猫吃了 软件的熵(无序)石头汤和煮青蛙你的知识财产 交流  查看详情

用尽洪荒之力学习flask源码

...ckLocalProxyContextCreateStackpushStackpopRequestResponseConfig一直想做源码阅读这件事,总感觉难度太高时间太少,可望不可见。最近正好时间充裕,决定试试做一下,并记录一下学习心得。首先说明一下,本文研究的Flask版本是0.12 查看详情

html5之历史记录api

<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><title>历史记录</title></head><body><inputtype="button"onclick="add()"value="添加一个历史记录"><scr 查看详情

git之清除历史记录操作

...时,不小心提交了敏感数据,如账号密码什么的,这样在历史记录中就可以查看到,这样很不安全,所以就需要吧历史提交记录删了,变成一个新的仓库。 1.创建一个新的分支(孤儿分支)gitcheckout--orphanlatest_branch 2.添加所... 查看详情

(三)接口自动化测试平台之——测试集合接口测试交互页面设计

...有用例的前提下才能删除)执行记录:查看该服务集合的历史执行记录用例来自哪里呢?答案如下(在创建用例的时候就选择了测试集合)新增|编辑服务执行服务集合点击执行服务集合测试用例(异步执行),前端页面会跳转... 查看详情

是否可以查看 chrome 历史记录的 ip 地址而不是 url?

】是否可以查看chrome历史记录的ip地址而不是url?【英文标题】:Isitpossibletoviewipaddressesofchromehistoryratherthanurls?【发布时间】:2012-07-2912:05:41【问题描述】:是否可以将chrome历史记录作为ip地址而不是urls来查看?原因是在我的历... 查看详情

transactionmanagement源码阅读路径(代码片段)

前言本文主要记录笔者学习TransactionManagement的学习路径,读者可以对比自己的学习路径,一起讨论出探讨出更优学习路径。基本路径找到官方文档。找到Overview页面,了解项目提供的能力、能实现的效果、设计理念、... 查看详情

transactionmanagement源码阅读路径(代码片段)

前言本文主要记录笔者学习TransactionManagement的学习路径,读者可以对比自己的学习路径,一起讨论出探讨出更优学习路径。基本路径找到官方文档。找到Overview页面,了解项目提供的能力、能实现的效果、设计理念、... 查看详情

恢复谷歌浏览器历史记录

参考技术A一、CHROME怎么找回历史记录你这个问题跟chrome关系不大,如果chrome在其他地方存储了浏览记录,那人们反而要为chrome的隐私安全担忧了。如果你装了扩展程序,某些扩展可能会记录浏览记录的,但是很难直接查看。推... 查看详情

(转)bootstrap之metronic模板的学习之路-源码分析之body部分

https://segmentfault.com/a/1190000006697252body的组成结构body部分包含了HEADER、CONTAINER、FOOTER,其中CONTAINER部分又包含了SIDEBAR、CONTENT、QUICKSIDEBAR几个部分。body部分源码折叠后截图如下:Header页面顶部Headercontainsoflogoandtopmenubaranditu 查看详情

teachingmachinestounderstandus让机器理解我们之二深度学习的历史

Deephistory深度学习的历史TherootsofdeeplearningreachbackfurtherthanLeCun’stimeatBellLabs.Heandafewotherswhopioneeredthetechniquewereactuallyresuscitatingalong-deadideainartificialintelligence.深度学习的研究之根是在LeCu 查看详情

(转)bootstrap之metronic模板的学习之路-源码分析之脚本部分

https://segmentfault.com/a/1190000006709967上篇我们将body标签主体部分进行了简单总览,下面看看最后的脚本部门。页面结尾部分(Javascripts脚本文件)我们来看看代码最后的代码,摘取如下:<!--[ifltIE9]><scriptsrc="../assets/global/plugins... 查看详情

之前chrome浏览器在搜索浏览历史的时候,可以通过把页面中的内容(不仅限标题)作为关键词来获取浏

之前Chrome浏览器在搜索浏览历史的时候,可以通过把页面中的内容(不仅限标题)作为关键词来获取浏览历史。这功能的原理是什么,他把网页中的文字都存本地数据库里了么?另外这功能现在的版本怎么没了搜索浏览历史,搜... 查看详情

历史记录如何删除

...-Internet选项。3、在Internet选项常规选项中勾选退出时删除历史记录,然后点击删除按钮进入设置界面。4、在删除设置界面勾选删除的内容,然后点击删除按钮,完成设置。对过去的一些人和事,通过各种方式保留下来,并能够... 查看详情

我们可以在我们的 android 应用程序中获取 chrome 浏览历史记录/书签吗

】我们可以在我们的android应用程序中获取chrome浏览历史记录/书签吗【英文标题】:canwegetchromebrowsinghistory/bookmarksinourandroidapp【发布时间】:2012-10-1916:40:37【问题描述】:我们能否像使用READ_HISTORY_BOOKMARKS权限在默认浏览器中一样... 查看详情