diff --git a/DEPS b/DEPS
index 33ee196..2f085716 100644
--- a/DEPS
+++ b/DEPS
@@ -44,7 +44,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': 'dabdeb4d7d6bc090233f756d160a622be5266611',
+  'v8_revision': '74c7e92b75798ba9d5d0b881ba4c8dcc6960f2ee',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling swarming_client
   # and whatever else without interference from each other.
diff --git a/cc/scheduler/scheduler.cc b/cc/scheduler/scheduler.cc
index 682944e..d68c962 100644
--- a/cc/scheduler/scheduler.cc
+++ b/cc/scheduler/scheduler.cc
@@ -204,6 +204,11 @@
   return begin_impl_frame_tracker_.Current().frame_time;
 }
 
+void Scheduler::BeginMainFrameNotExpectedUntil(base::TimeTicks time) {
+  TRACE_EVENT1("cc", "Scheduler::BeginMainFrameNotExpectedUntil", "time", time);
+  client_->ScheduledActionBeginMainFrameNotExpectedUntil(time);
+}
+
 void Scheduler::BeginImplFrameNotExpectedSoon() {
   compositor_timing_history_->BeginImplFrameNotExpectedSoon();
 
@@ -653,6 +658,11 @@
         // TODO(brianderson): Pass begin_main_frame_args_ directly to client.
         client_->ScheduledActionSendBeginMainFrame(begin_main_frame_args_);
         break;
+      case SchedulerStateMachine::ACTION_NOTIFY_BEGIN_MAIN_FRAME_NOT_SENT:
+        state_machine_.WillNotifyBeginMainFrameNotSent();
+        BeginMainFrameNotExpectedUntil(begin_main_frame_args_.frame_time +
+                                       begin_main_frame_args_.interval);
+        break;
       case SchedulerStateMachine::ACTION_COMMIT: {
         bool commit_has_no_updates = false;
         state_machine_.WillCommit(commit_has_no_updates);
diff --git a/cc/scheduler/scheduler.h b/cc/scheduler/scheduler.h
index ed609b31..69bc7739 100644
--- a/cc/scheduler/scheduler.h
+++ b/cc/scheduler/scheduler.h
@@ -48,6 +48,8 @@
   virtual void ScheduledActionPerformImplSideInvalidation() = 0;
   virtual void DidFinishImplFrame() = 0;
   virtual void SendBeginMainFrameNotExpectedSoon() = 0;
+  virtual void ScheduledActionBeginMainFrameNotExpectedUntil(
+      base::TimeTicks time) = 0;
 
  protected:
   virtual ~SchedulerClient() {}
@@ -197,6 +199,7 @@
   void ScheduleBeginImplFrameDeadline();
   void ScheduleBeginImplFrameDeadlineIfNeeded();
   void BeginImplFrameNotExpectedSoon();
+  void BeginMainFrameNotExpectedUntil(base::TimeTicks time);
   void SetupNextBeginFrameIfNeeded();
   void StartObservingBeginFrameSource();
   void StopObservingBeginFrameSource();
diff --git a/cc/scheduler/scheduler_state_machine.cc b/cc/scheduler/scheduler_state_machine.cc
index 9551b43..210a2bd 100644
--- a/cc/scheduler/scheduler_state_machine.cc
+++ b/cc/scheduler/scheduler_state_machine.cc
@@ -140,6 +140,8 @@
       return "ACTION_INVALIDATE_COMPOSITOR_FRAME_SINK";
     case ACTION_PERFORM_IMPL_SIDE_INVALIDATION:
       return "ACTION_PERFORM_IMPL_SIDE_INVALIDATION";
+    case ACTION_NOTIFY_BEGIN_MAIN_FRAME_NOT_SENT:
+      return "ACTION_NOTIFY_BEGIN_MAIN_FRAME_NOT_SENT";
   }
   NOTREACHED();
   return "???";
@@ -190,7 +192,11 @@
       "last_begin_frame_sequence_number_compositor_frame_was_fresh",
       last_begin_frame_sequence_number_compositor_frame_was_fresh_);
   state->SetBoolean("did_draw", did_draw_);
-  state->SetBoolean("did_send_begin_main_frame", did_send_begin_main_frame_);
+  state->SetBoolean("did_send_begin_main_frame_for_current_frame",
+                    did_send_begin_main_frame_for_current_frame_);
+  state->SetBoolean("did_notify_begin_main_frame_not_sent",
+                    did_notify_begin_main_frame_not_sent_);
+  state->SetBoolean("did_commit_during_frame", did_commit_during_frame_);
   state->SetBoolean("did_invalidate_compositor_frame_sink",
                     did_invalidate_compositor_frame_sink_);
   state->SetBoolean("did_perform_impl_side_invalidaion",
@@ -376,6 +382,42 @@
   return pending_tree_is_ready_for_activation_;
 }
 
+bool SchedulerStateMachine::ShouldNotifyBeginMainFrameNotSent() const {
+  // This method returns true if most of the conditions for sending a
+  // BeginMainFrame are met, but one is not actually requested. This gives the
+  // main thread the chance to do something else.
+
+  // Don't notify if a BeginMainFrame has already been requested or is in
+  // progress.
+  if (needs_begin_main_frame_ ||
+      begin_main_frame_state_ != BEGIN_MAIN_FRAME_STATE_IDLE)
+    return false;
+
+  // Only notify when we're visible.
+  if (!visible_)
+    return false;
+
+  // There are no BeginImplFrames while BeginFrameSource is paused, meaning
+  // the scheduler should send SendBeginMainFrameNotExpectedSoon instead,
+  // indicating a longer period of inactivity.
+  if (begin_frame_source_paused_)
+    return false;
+
+  // Do not notify that no BeginMainFrame was sent too many times in a single
+  // frame.
+  if (did_notify_begin_main_frame_not_sent_)
+    return false;
+
+  // Do not notify if a commit happened during this frame as the main thread
+  // will already be active and does not need to be woken up to make further
+  // actions. (This occurs if the main frame was scheduled but didn't complete
+  // before the vsync deadline).
+  if (did_commit_during_frame_)
+    return false;
+
+  return true;
+}
+
 bool SchedulerStateMachine::CouldSendBeginMainFrame() const {
   if (!needs_begin_main_frame_)
     return false;
@@ -401,7 +443,7 @@
     return false;
 
   // Do not send more than one begin main frame in a begin frame.
-  if (did_send_begin_main_frame_)
+  if (did_send_begin_main_frame_for_current_frame_)
     return false;
 
   // Only send BeginMainFrame when there isn't another commit pending already.
@@ -544,6 +586,8 @@
     return ACTION_INVALIDATE_COMPOSITOR_FRAME_SINK;
   if (ShouldBeginCompositorFrameSinkCreation())
     return ACTION_BEGIN_COMPOSITOR_FRAME_SINK_CREATION;
+  if (ShouldNotifyBeginMainFrameNotSent())
+    return ACTION_NOTIFY_BEGIN_MAIN_FRAME_NOT_SENT;
   return ACTION_NONE;
 }
 
@@ -620,15 +664,22 @@
   DCHECK(!has_pending_tree_ || settings_.main_frame_before_activation_enabled);
   DCHECK(visible_);
   DCHECK(!begin_frame_source_paused_);
-  DCHECK(!did_send_begin_main_frame_);
+  DCHECK(!did_send_begin_main_frame_for_current_frame_);
   begin_main_frame_state_ = BEGIN_MAIN_FRAME_STATE_SENT;
   needs_begin_main_frame_ = false;
-  did_send_begin_main_frame_ = true;
+  did_send_begin_main_frame_for_current_frame_ = true;
   last_frame_number_begin_main_frame_sent_ = current_frame_number_;
   last_begin_frame_sequence_number_begin_main_frame_sent_ =
       begin_frame_sequence_number_;
 }
 
+void SchedulerStateMachine::WillNotifyBeginMainFrameNotSent() {
+  DCHECK(visible_);
+  DCHECK(!begin_frame_source_paused_);
+  DCHECK(!did_notify_begin_main_frame_not_sent_);
+  did_notify_begin_main_frame_not_sent_ = true;
+}
+
 void SchedulerStateMachine::WillCommit(bool commit_has_no_updates) {
   bool can_have_pending_tree =
       commit_has_no_updates &&
@@ -638,6 +689,7 @@
   commit_count_++;
   last_commit_had_no_updates_ = commit_has_no_updates;
   begin_main_frame_state_ = BEGIN_MAIN_FRAME_STATE_IDLE;
+  did_commit_during_frame_ = true;
 
   if (commit_has_no_updates) {
     // Pending tree might still exist from prior commit.
@@ -983,7 +1035,9 @@
   did_submit_in_last_frame_ = false;
   needs_one_begin_impl_frame_ = false;
 
-  did_send_begin_main_frame_ = false;
+  did_notify_begin_main_frame_not_sent_ = false;
+  did_send_begin_main_frame_for_current_frame_ = false;
+  did_commit_during_frame_ = false;
   did_invalidate_compositor_frame_sink_ = false;
   did_perform_impl_side_invalidation_ = false;
 }
@@ -1017,7 +1071,7 @@
   // If we're entering a state where we won't get BeginFrames set all the
   // funnels so that we don't perform any actions that we shouldn't.
   if (!BeginFrameNeeded())
-    did_send_begin_main_frame_ = true;
+    did_send_begin_main_frame_for_current_frame_ = true;
 
   // Synchronous compositor finishes BeginFrames before triggering their
   // deadline. Therefore, we update sequence numbers when becoming idle, before
diff --git a/cc/scheduler/scheduler_state_machine.h b/cc/scheduler/scheduler_state_machine.h
index 01528b8..6656188d 100644
--- a/cc/scheduler/scheduler_state_machine.h
+++ b/cc/scheduler/scheduler_state_machine.h
@@ -125,6 +125,7 @@
     ACTION_BEGIN_COMPOSITOR_FRAME_SINK_CREATION,
     ACTION_PREPARE_TILES,
     ACTION_INVALIDATE_COMPOSITOR_FRAME_SINK,
+    ACTION_NOTIFY_BEGIN_MAIN_FRAME_NOT_SENT,
   };
   static const char* ActionToString(Action action);
 
@@ -133,6 +134,7 @@
 
   Action NextAction() const;
   void WillSendBeginMainFrame();
+  void WillNotifyBeginMainFrameNotSent();
   void WillCommit(bool commit_had_no_updates);
   void WillActivate();
   void WillDraw();
@@ -320,6 +322,7 @@
   bool ShouldCommit() const;
   bool ShouldPrepareTiles() const;
   bool ShouldInvalidateCompositorFrameSink() const;
+  bool ShouldNotifyBeginMainFrameNotSent() const;
 
   void WillDrawInternal();
   void WillPerformImplSideInvalidationInternal();
@@ -363,9 +366,11 @@
   // These are used to ensure that an action only happens once per frame,
   // deadline, etc.
   bool did_draw_ = false;
+  bool did_send_begin_main_frame_for_current_frame_ = true;
   // Initialized to true to prevent begin main frame before begin frames have
   // started. Reset to true when we stop asking for begin frames.
-  bool did_send_begin_main_frame_ = true;
+  bool did_notify_begin_main_frame_not_sent_ = true;
+  bool did_commit_during_frame_ = false;
   bool did_invalidate_compositor_frame_sink_ = false;
   bool did_perform_impl_side_invalidation_ = false;
   bool did_prepare_tiles_ = false;
diff --git a/cc/scheduler/scheduler_state_machine_unittest.cc b/cc/scheduler/scheduler_state_machine_unittest.cc
index 0a99a09..da53e5de 100644
--- a/cc/scheduler/scheduler_state_machine_unittest.cc
+++ b/cc/scheduler/scheduler_state_machine_unittest.cc
@@ -204,6 +204,10 @@
       sm->WillSendBeginMainFrame();
       return;
 
+    case SchedulerStateMachine::ACTION_NOTIFY_BEGIN_MAIN_FRAME_NOT_SENT:
+      sm->WillNotifyBeginMainFrameNotSent();
+      return;
+
     case SchedulerStateMachine::ACTION_COMMIT: {
       bool commit_has_no_updates = false;
       sm->WillCommit(commit_has_no_updates);
@@ -290,6 +294,27 @@
   EXPECT_FALSE(state.BeginFrameNeeded());
 }
 
+TEST(SchedulerStateMachineTest, TestNextActionNotifyBeginMainFrameNotSent) {
+  SchedulerSettings default_scheduler_settings;
+  StateMachine state(default_scheduler_settings);
+  state.SetVisible(true);
+  EXPECT_ACTION_UPDATE_STATE(
+      SchedulerStateMachine::ACTION_BEGIN_COMPOSITOR_FRAME_SINK_CREATION);
+  state.IssueNextBeginImplFrame();
+  state.CreateAndInitializeCompositorFrameSinkWithActivatedCommit();
+  // A main frame was not requested so short idle work is scheduled instead.
+  EXPECT_ACTION_UPDATE_STATE(
+      SchedulerStateMachine::ACTION_NOTIFY_BEGIN_MAIN_FRAME_NOT_SENT);
+  EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE);
+
+  state.SetNeedsRedraw(true);
+  state.SetNeedsBeginMainFrame();
+  // Now a main frame is requested no short idle work is scheduled.
+  EXPECT_ACTION_UPDATE_STATE(
+      SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME);
+  EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE);
+}
+
 TEST(SchedulerStateMachineTest, TestNextActionBeginsMainFrameIfNeeded) {
   SchedulerSettings default_scheduler_settings;
 
@@ -309,6 +334,8 @@
     EXPECT_FALSE(state.NeedsCommit());
 
     state.IssueNextBeginImplFrame();
+    EXPECT_ACTION_UPDATE_STATE(
+        SchedulerStateMachine::ACTION_NOTIFY_BEGIN_MAIN_FRAME_NOT_SENT);
     EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE);
 
     state.OnBeginImplFrameDeadline();
@@ -458,6 +485,8 @@
 
   // Start a frame.
   state.IssueNextBeginImplFrame();
+  EXPECT_ACTION_UPDATE_STATE(
+      SchedulerStateMachine::ACTION_NOTIFY_BEGIN_MAIN_FRAME_NOT_SENT);
   EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE);
   EXPECT_FALSE(state.CommitPending());
 
@@ -495,6 +524,8 @@
 
   // Start a frame.
   state.IssueNextBeginImplFrame();
+  EXPECT_ACTION_UPDATE_STATE(
+      SchedulerStateMachine::ACTION_NOTIFY_BEGIN_MAIN_FRAME_NOT_SENT);
   EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE);
   EXPECT_FALSE(state.CommitPending());
 
@@ -531,6 +562,8 @@
 
   // Verify we draw with the new frame.
   state.IssueNextBeginImplFrame();
+  EXPECT_ACTION_UPDATE_STATE(
+      SchedulerStateMachine::ACTION_NOTIFY_BEGIN_MAIN_FRAME_NOT_SENT);
   EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE);
   state.OnBeginImplFrameDeadline();
   state.SetDrawResultForTest(DRAW_SUCCESS);
@@ -664,6 +697,8 @@
   state.SetNeedsRedraw(true);
   EXPECT_TRUE(state.BeginFrameNeeded());
   state.IssueNextBeginImplFrame();
+  EXPECT_ACTION_UPDATE_STATE(
+      SchedulerStateMachine::ACTION_NOTIFY_BEGIN_MAIN_FRAME_NOT_SENT);
   EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE);
   state.OnBeginImplFrameDeadline();
   EXPECT_TRUE(state.RedrawPending());
@@ -700,6 +735,8 @@
   // Draw the first frame.
   EXPECT_TRUE(state.BeginFrameNeeded());
   state.IssueNextBeginImplFrame();
+  EXPECT_ACTION_UPDATE_STATE(
+      SchedulerStateMachine::ACTION_NOTIFY_BEGIN_MAIN_FRAME_NOT_SENT);
   EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE);
 
   state.OnBeginImplFrameDeadline();
@@ -716,6 +753,8 @@
   // Move to another frame. This should now draw.
   EXPECT_TRUE(state.BeginFrameNeeded());
   state.IssueNextBeginImplFrame();
+  EXPECT_ACTION_UPDATE_STATE(
+      SchedulerStateMachine::ACTION_NOTIFY_BEGIN_MAIN_FRAME_NOT_SENT);
   EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE);
   state.OnBeginImplFrameDeadline();
   EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE);
@@ -1400,6 +1439,8 @@
   EXPECT_MAIN_FRAME_STATE(SchedulerStateMachine::BEGIN_MAIN_FRAME_STATE_IDLE);
 
   state.IssueNextBeginImplFrame();
+  EXPECT_ACTION_UPDATE_STATE(
+      SchedulerStateMachine::ACTION_NOTIFY_BEGIN_MAIN_FRAME_NOT_SENT);
   EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE);
   state.OnBeginImplFrameDeadline();
   EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE);
@@ -1424,6 +1465,8 @@
 
   // Check that the first init does not SetNeedsBeginMainFrame.
   state.IssueNextBeginImplFrame();
+  EXPECT_ACTION_UPDATE_STATE(
+      SchedulerStateMachine::ACTION_NOTIFY_BEGIN_MAIN_FRAME_NOT_SENT);
   EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE);
   state.OnBeginImplFrameDeadline();
   EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE);
@@ -1478,6 +1521,8 @@
 
   // Once context recreation begins, nothing should happen.
   state.IssueNextBeginImplFrame();
+  EXPECT_ACTION_UPDATE_STATE(
+      SchedulerStateMachine::ACTION_NOTIFY_BEGIN_MAIN_FRAME_NOT_SENT);
   EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE);
   state.OnBeginImplFrameDeadline();
   EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE);
@@ -1528,6 +1573,8 @@
   // automatically cause a redraw.
   EXPECT_TRUE(state.RedrawPending());
   state.IssueNextBeginImplFrame();
+  EXPECT_ACTION_UPDATE_STATE(
+      SchedulerStateMachine::ACTION_NOTIFY_BEGIN_MAIN_FRAME_NOT_SENT);
   EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE);
   state.OnBeginImplFrameDeadline();
   EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE);
@@ -1536,6 +1583,8 @@
 
   // Next frame as no work to do.
   state.IssueNextBeginImplFrame();
+  EXPECT_ACTION_UPDATE_STATE(
+      SchedulerStateMachine::ACTION_NOTIFY_BEGIN_MAIN_FRAME_NOT_SENT);
   EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE);
   state.OnBeginImplFrameDeadline();
   EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE);
@@ -1544,6 +1593,8 @@
   // SetCanDraw if waiting on first draw after activate.
   state.SetNeedsRedraw(true);
   state.IssueNextBeginImplFrame();
+  EXPECT_ACTION_UPDATE_STATE(
+      SchedulerStateMachine::ACTION_NOTIFY_BEGIN_MAIN_FRAME_NOT_SENT);
   EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE);
   state.OnBeginImplFrameDeadline();
   EXPECT_ACTION(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE);
@@ -1636,6 +1687,8 @@
   state.OnBeginImplFrame(0, 11);
   EXPECT_IMPL_FRAME_STATE(
       SchedulerStateMachine::BEGIN_IMPL_FRAME_STATE_INSIDE_BEGIN_FRAME);
+  EXPECT_ACTION_UPDATE_STATE(
+      SchedulerStateMachine::ACTION_NOTIFY_BEGIN_MAIN_FRAME_NOT_SENT);
   EXPECT_ACTION(SchedulerStateMachine::ACTION_NONE);
   EXPECT_SEQUENCE_NUMBERS(11u, 10u, 10u, 10u,
                           BeginFrameArgs::kInvalidFrameNumber);
@@ -1993,7 +2046,8 @@
 }
 
 void FinishPreviousCommitAndDrawWithoutExitingDeadline(
-    StateMachine* state_ptr) {
+    StateMachine* state_ptr,
+    bool expect_begin_main_frame_not_sent_before_impl_frame_deadline) {
   // Gross, but allows us to use macros below.
   StateMachine& state = *state_ptr;
 
@@ -2006,6 +2060,10 @@
   EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE);
 
   state.IssueNextBeginImplFrame();
+  if (expect_begin_main_frame_not_sent_before_impl_frame_deadline) {
+    EXPECT_ACTION_UPDATE_STATE(
+        SchedulerStateMachine::ACTION_NOTIFY_BEGIN_MAIN_FRAME_NOT_SENT);
+  }
   EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE);
 
   EXPECT_TRUE(state.ShouldTriggerBeginImplFrameDeadlineImmediately());
@@ -2045,7 +2103,7 @@
 
   // Request a new commit and finish the previous one.
   state.SetNeedsBeginMainFrame();
-  FinishPreviousCommitAndDrawWithoutExitingDeadline(&state);
+  FinishPreviousCommitAndDrawWithoutExitingDeadline(&state, false);
   EXPECT_ACTION_UPDATE_STATE(
       SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME);
   EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE);
@@ -2053,7 +2111,7 @@
   EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE);
 
   // Finish the previous commit and draw it.
-  FinishPreviousCommitAndDrawWithoutExitingDeadline(&state);
+  FinishPreviousCommitAndDrawWithoutExitingDeadline(&state, true);
   EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE);
 
   // Verify we do not send another BeginMainFrame if was are submit-frame
@@ -2234,6 +2292,8 @@
 
   state.SetNeedsImplSideInvalidation();
   state.IssueNextBeginImplFrame();
+  EXPECT_ACTION_UPDATE_STATE(
+      SchedulerStateMachine::ACTION_NOTIFY_BEGIN_MAIN_FRAME_NOT_SENT);
   EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE);
   state.OnBeginImplFrameDeadline();
   EXPECT_ACTION_UPDATE_STATE(
@@ -2257,6 +2317,8 @@
   state.SetNeedsImplSideInvalidation();
   state.IssueNextBeginImplFrame();
   state.OnBeginImplFrameDeadline();
+  EXPECT_ACTION_UPDATE_STATE(
+      SchedulerStateMachine::ACTION_NOTIFY_BEGIN_MAIN_FRAME_NOT_SENT);
   EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE);
 
   // Initializing the CompositorFrameSink puts us in a state waiting for the
@@ -2351,6 +2413,8 @@
   state.OnBeginImplFrameDeadline();
   EXPECT_ACTION_UPDATE_STATE(
       SchedulerStateMachine::ACTION_PERFORM_IMPL_SIDE_INVALIDATION);
+  EXPECT_ACTION_UPDATE_STATE(
+      SchedulerStateMachine::ACTION_NOTIFY_BEGIN_MAIN_FRAME_NOT_SENT);
   EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE);
 
   // Request another invalidation, which should wait until the pending tree is
@@ -2399,6 +2463,8 @@
   state.SetNeedsImplSideInvalidation();
   state.IssueNextBeginImplFrame();
   state.OnBeginImplFrameDeadline();
+  EXPECT_ACTION_UPDATE_STATE(
+      SchedulerStateMachine::ACTION_NOTIFY_BEGIN_MAIN_FRAME_NOT_SENT);
   EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE);
 
   // Ack the previous frame and begin impl frame, which should perform the
@@ -2447,6 +2513,8 @@
   EXPECT_ACTION_UPDATE_STATE(
       SchedulerStateMachine::ACTION_PERFORM_IMPL_SIDE_INVALIDATION);
   state.DidPrepareTiles();
+  EXPECT_ACTION_UPDATE_STATE(
+      SchedulerStateMachine::ACTION_NOTIFY_BEGIN_MAIN_FRAME_NOT_SENT);
   EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE);
 }
 
@@ -2473,6 +2541,8 @@
 
   // OnBeginImplFrame() updates the sequence number.
   state.OnBeginImplFrame(0, 10);
+  EXPECT_ACTION_UPDATE_STATE(
+      SchedulerStateMachine::ACTION_NOTIFY_BEGIN_MAIN_FRAME_NOT_SENT);
   EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE);
   EXPECT_SEQUENCE_NUMBERS(10u, BeginFrameArgs::kInvalidFrameNumber,
                           BeginFrameArgs::kInvalidFrameNumber,
@@ -2500,6 +2570,8 @@
 
   // OnBeginImplFrame() updates the sequence number.
   state.OnBeginImplFrame(0, 10);
+  EXPECT_ACTION_UPDATE_STATE(
+      SchedulerStateMachine::ACTION_NOTIFY_BEGIN_MAIN_FRAME_NOT_SENT);
   EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE);
   EXPECT_SEQUENCE_NUMBERS(10u, BeginFrameArgs::kInvalidFrameNumber,
                           BeginFrameArgs::kInvalidFrameNumber,
@@ -2583,6 +2655,8 @@
   // If no further BeginMainFrame is needed, OnBeginFrameImplDeadline()
   // updates the pending tree's frame number.
   state.OnBeginImplFrame(0, 12);
+  EXPECT_ACTION_UPDATE_STATE(
+      SchedulerStateMachine::ACTION_NOTIFY_BEGIN_MAIN_FRAME_NOT_SENT);
   EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE);
   EXPECT_SEQUENCE_NUMBERS(12u, 10u, 10u, BeginFrameArgs::kInvalidFrameNumber,
                           BeginFrameArgs::kInvalidFrameNumber);
@@ -2644,6 +2718,8 @@
   // When no updates are required, OnBeginImplFrameDeadline() updates active
   // tree and compositor frame freshness.
   state.OnBeginImplFrame(0, 15);
+  EXPECT_ACTION_UPDATE_STATE(
+      SchedulerStateMachine::ACTION_NOTIFY_BEGIN_MAIN_FRAME_NOT_SENT);
   EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE);
   state.OnBeginImplFrameDeadline();
   EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE);
@@ -2673,6 +2749,8 @@
   // When the source changes, the current frame number is updated and frame
   // numbers for freshness are reset to invalid numbers.
   state.OnBeginImplFrame(1, 5);
+  EXPECT_ACTION_UPDATE_STATE(
+      SchedulerStateMachine::ACTION_NOTIFY_BEGIN_MAIN_FRAME_NOT_SENT);
   EXPECT_ACTION_UPDATE_STATE(SchedulerStateMachine::ACTION_NONE);
   EXPECT_SEQUENCE_NUMBERS(5u, BeginFrameArgs::kInvalidFrameNumber,
                           BeginFrameArgs::kInvalidFrameNumber,
diff --git a/cc/scheduler/scheduler_unittest.cc b/cc/scheduler/scheduler_unittest.cc
index b7b00127..a551f596 100644
--- a/cc/scheduler/scheduler_unittest.cc
+++ b/cc/scheduler/scheduler_unittest.cc
@@ -179,6 +179,11 @@
     PushAction("SendBeginMainFrameNotExpectedSoon");
   }
 
+  void ScheduledActionBeginMainFrameNotExpectedUntil(
+      base::TimeTicks time) override {
+    PushAction("ScheduledActionBeginMainFrameNotExpectedUntil");
+  }
+
   bool IsInsideBeginImplFrame() const { return inside_begin_impl_frame_; }
 
   base::Callback<bool(void)> InsideBeginImplFrame(bool state) {
@@ -485,12 +490,14 @@
   EXPECT_SCOPED(AdvanceFrame());
   EXPECT_TRUE(client_->IsInsideBeginImplFrame());
   // WillBeginImplFrame is responsible for sending BeginFrames to video.
-  EXPECT_SINGLE_ACTION("WillBeginImplFrame", client_);
+  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 2);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 1, 2);
 
   client_->Reset();
   EXPECT_SCOPED(AdvanceFrame());
   EXPECT_TRUE(client_->IsInsideBeginImplFrame());
-  EXPECT_SINGLE_ACTION("WillBeginImplFrame", client_);
+  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 2);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 1, 2);
 
   client_->Reset();
   scheduler_->SetVideoNeedsBeginFrames(false);
@@ -541,7 +548,8 @@
 
   // BeginImplFrame should prepare the draw.
   EXPECT_SCOPED(AdvanceFrame());
-  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 1);
+  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 2);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 1, 2);
   EXPECT_TRUE(client_->IsInsideBeginImplFrame());
   EXPECT_TRUE(scheduler_->begin_frames_expected());
   client_->Reset();
@@ -556,7 +564,8 @@
   // The following BeginImplFrame deadline should SetNeedsBeginFrame(false)
   // to avoid excessive toggles.
   EXPECT_SCOPED(AdvanceFrame());
-  EXPECT_SINGLE_ACTION("WillBeginImplFrame", client_);
+  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 2);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 1, 2);
   EXPECT_TRUE(client_->IsInsideBeginImplFrame());
   client_->Reset();
 
@@ -976,7 +985,8 @@
   // the deadline task.
   client->Reset();
   EXPECT_SCOPED(AdvanceFrame());
-  EXPECT_ACTION("WillBeginImplFrame", client, 0, 1);
+  EXPECT_ACTION("WillBeginImplFrame", client, 0, 2);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client, 1, 2);
   EXPECT_TRUE(client_->IsInsideBeginImplFrame());
 
   // On the deadline, the actions should have occured in the right order.
@@ -1003,7 +1013,8 @@
   // the deadline task.
   client->Reset();
   EXPECT_SCOPED(AdvanceFrame());
-  EXPECT_ACTION("WillBeginImplFrame", client, 0, 1);
+  EXPECT_ACTION("WillBeginImplFrame", client, 0, 2);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client, 1, 2);
   EXPECT_TRUE(client_->IsInsideBeginImplFrame());
 
   // Draw. The draw will trigger SetNeedsPrepareTiles, and
@@ -1023,7 +1034,8 @@
   // We need a BeginImplFrame where we don't swap to go idle.
   client->Reset();
   EXPECT_SCOPED(AdvanceFrame());
-  EXPECT_SINGLE_ACTION("WillBeginImplFrame", client);
+  EXPECT_ACTION("WillBeginImplFrame", client, 0, 2);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client, 1, 2);
   EXPECT_TRUE(client_->IsInsideBeginImplFrame());
   client->Reset();
   task_runner().RunPendingTasks();  // Run posted deadline.
@@ -1044,7 +1056,8 @@
   // BeginImplFrame. There will be no draw, only PrepareTiles.
   client->Reset();
   EXPECT_SCOPED(AdvanceFrame());
-  EXPECT_SINGLE_ACTION("WillBeginImplFrame", client);
+  EXPECT_ACTION("WillBeginImplFrame", client, 0, 2);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client, 1, 2);
   EXPECT_TRUE(client_->IsInsideBeginImplFrame());
   client->Reset();
   task_runner().RunPendingTasks();  // Run posted deadline.
@@ -1065,7 +1078,8 @@
   scheduler_->SetNeedsRedraw();
   client_->Reset();
   EXPECT_SCOPED(AdvanceFrame());
-  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 1);
+  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 2);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 1, 2);
   EXPECT_TRUE(client_->IsInsideBeginImplFrame());
 
   EXPECT_TRUE(scheduler_->PrepareTilesPending());
@@ -1087,7 +1101,8 @@
   scheduler_->SetNeedsRedraw();
   client_->Reset();
   EXPECT_SCOPED(AdvanceFrame());
-  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 1);
+  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 2);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 1, 2);
   EXPECT_TRUE(client_->IsInsideBeginImplFrame());
 
   client_->Reset();
@@ -1109,7 +1124,8 @@
   scheduler_->SetNeedsRedraw();
   client_->Reset();
   EXPECT_SCOPED(AdvanceFrame());
-  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 1);
+  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 2);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 1, 2);
   EXPECT_TRUE(client_->IsInsideBeginImplFrame());
 
   EXPECT_TRUE(scheduler_->PrepareTilesPending());
@@ -1133,7 +1149,8 @@
   scheduler_->SetNeedsRedraw();
   client_->Reset();
   EXPECT_SCOPED(AdvanceFrame());
-  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 1);
+  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 2);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 1, 2);
   EXPECT_TRUE(client_->IsInsideBeginImplFrame());
 
   EXPECT_TRUE(scheduler_->PrepareTilesPending());
@@ -1151,7 +1168,8 @@
   scheduler_->SetNeedsRedraw();
   client_->Reset();
   EXPECT_SCOPED(AdvanceFrame());
-  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 1);
+  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 2);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 1, 2);
   EXPECT_TRUE(client_->IsInsideBeginImplFrame());
 
   client_->Reset();
@@ -1177,7 +1195,8 @@
 
   client_->Reset();
   EXPECT_SCOPED(AdvanceFrame());
-  EXPECT_SINGLE_ACTION("WillBeginImplFrame", client_);
+  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 2);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 1, 2);
   EXPECT_TRUE(client_->IsInsideBeginImplFrame());
 
   client_->Reset();
@@ -1196,7 +1215,8 @@
   client_->Reset();
   scheduler_->SetNeedsRedraw();
   EXPECT_SCOPED(AdvanceFrame());
-  EXPECT_SINGLE_ACTION("WillBeginImplFrame", client_);
+  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 2);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 1, 2);
   EXPECT_TRUE(client_->IsInsideBeginImplFrame());
 
   // No scheduled prepare tiles because we've already counted a prepare tiles in
@@ -1209,7 +1229,8 @@
   client_->Reset();
   scheduler_->SetNeedsRedraw();
   EXPECT_SCOPED(AdvanceFrame());
-  EXPECT_SINGLE_ACTION("WillBeginImplFrame", client_);
+  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 2);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 1, 2);
   EXPECT_TRUE(client_->IsInsideBeginImplFrame());
 
   // Resume scheduled prepare tiles.
@@ -1697,8 +1718,9 @@
   scheduler_->SetNeedsRedraw();
   EXPECT_FALSE(scheduler_->MainThreadMissedLastDeadline());
   SendNextBeginFrame();
-  EXPECT_ACTION("AddObserver(this)", client_, 0, 2);
-  EXPECT_ACTION("WillBeginImplFrame", client_, 1, 2);
+  EXPECT_ACTION("AddObserver(this)", client_, 0, 3);
+  EXPECT_ACTION("WillBeginImplFrame", client_, 1, 3);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 2, 3);
 
   client_->Reset();
   EXPECT_FALSE(scheduler_->MainThreadMissedLastDeadline());
@@ -1730,7 +1752,9 @@
     client_->Reset();
     EXPECT_FALSE(scheduler_->MainThreadMissedLastDeadline());
     SendNextBeginFrame();
-    EXPECT_ACTION("WillBeginImplFrame", client_, 0, 1);
+    EXPECT_ACTION("WillBeginImplFrame", client_, 0, 2);
+    EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 1,
+                  2);
 
     client_->Reset();
     // Deadline should be immediate.
@@ -2108,7 +2132,7 @@
   // NotifyReadyToCommit should trigger the commit.
   scheduler_->NotifyBeginMainFrameStarted(base::TimeTicks());
   scheduler_->NotifyReadyToCommit();
-  EXPECT_SINGLE_ACTION("ScheduledActionCommit", client_);
+  EXPECT_ACTION("ScheduledActionCommit", client_, 0, 1);
   client_->Reset();
 
   // NotifyReadyToActivate should trigger the activation.
@@ -2119,8 +2143,9 @@
   // BeginImplFrame deadline should draw. The following BeginImplFrame deadline
   // should SetNeedsBeginFrame(false) to avoid excessive toggles.
   EXPECT_SCOPED(AdvanceFrame());
-  EXPECT_ACTION("ScheduledActionDrawIfPossible", client_, 0, 2);
-  EXPECT_ACTION("WillBeginImplFrame", client_, 1, 2);
+  EXPECT_ACTION("ScheduledActionDrawIfPossible", client_, 0, 3);
+  EXPECT_ACTION("WillBeginImplFrame", client_, 1, 3);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 2, 3);
   client_->Reset();
 
   // Make sure SetNeedsBeginFrame isn't called on the client
@@ -2371,7 +2396,8 @@
 
   client_->Reset();
   EXPECT_SCOPED(AdvanceFrame());
-  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 1);
+  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 2);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 1, 2);
   EXPECT_TRUE(client_->IsInsideBeginImplFrame());
 
   client_->Reset();
@@ -2532,7 +2558,8 @@
   client_->Reset();
 
   EXPECT_SCOPED(AdvanceFrame());
-  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 1);
+  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 2);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 1, 2);
   EXPECT_TRUE(client_->IsInsideBeginImplFrame());
   EXPECT_TRUE(scheduler_->begin_frames_expected());
   client_->Reset();
@@ -2546,7 +2573,8 @@
 
   // Unthrottled frame source will immediately begin a new frame.
   task_runner().RunPendingTasks();  // Run posted BeginFrame.
-  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 1);
+  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 2);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 1, 2);
   EXPECT_TRUE(client_->IsInsideBeginImplFrame());
   client_->Reset();
 
@@ -2568,7 +2596,8 @@
   client_->Reset();
 
   EXPECT_SCOPED(AdvanceFrame());
-  EXPECT_SINGLE_ACTION("WillBeginImplFrame", client_);
+  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 2);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 1, 2);
 
   // Switch to an unthrottled frame source before the frame deadline is hit.
   scheduler_->SetBeginFrameSource(unthrottled_frame_source_.get());
@@ -2579,9 +2608,10 @@
   client_->Reset();
 
   task_runner().RunPendingTasks();  // Run posted deadline.
-  EXPECT_ACTION("ScheduledActionDrawIfPossible", client_, 0, 2);
+  EXPECT_ACTION("ScheduledActionDrawIfPossible", client_, 0, 3);
   // Unthrottled frame source will immediately begin a new frame.
-  EXPECT_ACTION("WillBeginImplFrame", client_, 1, 2);
+  EXPECT_ACTION("WillBeginImplFrame", client_, 1, 3);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 2, 3);
   scheduler_->SetNeedsRedraw();
   client_->Reset();
 
@@ -2601,7 +2631,8 @@
   client_->Reset();
 
   task_runner().RunPendingTasks();  // Run posted BeginFrame.
-  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 1);
+  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 2);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 1, 2);
   EXPECT_TRUE(client_->IsInsideBeginImplFrame());
   client_->Reset();
 
@@ -2621,7 +2652,8 @@
   client_->Reset();
 
   EXPECT_SCOPED(AdvanceFrame());
-  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 1);
+  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 2);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 1, 2);
   EXPECT_TRUE(client_->IsInsideBeginImplFrame());
   EXPECT_TRUE(scheduler_->begin_frames_expected());
   client_->Reset();
@@ -2637,7 +2669,8 @@
   client_->Reset();
 
   EXPECT_SCOPED(AdvanceFrame());
-  EXPECT_SINGLE_ACTION("WillBeginImplFrame", client_);
+  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 2);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 1, 2);
   client_->Reset();
 
   // Switch to a null frame source.
@@ -2734,6 +2767,23 @@
   EXPECT_ACTION("ScheduledActionSendBeginMainFrame", client_, 1, 2);
 }
 
+// Tests to ensure that we send a ScheduledActionBeginMainFrameNotExpectedUntil
+// when expected.
+TEST_F(SchedulerTest, ScheduledActionBeginMainFrameNotExpectedUntil) {
+  SetUpScheduler(EXTERNAL_BFS);
+
+  scheduler_->SetNeedsRedraw();
+  EXPECT_ACTION("AddObserver(this)", client_, 0, 1);
+  client_->Reset();
+
+  EXPECT_SCOPED(AdvanceFrame());
+  task_runner().RunPendingTasks();
+  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 3);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 1, 3);
+  EXPECT_ACTION("ScheduledActionDrawIfPossible", client_, 2, 3);
+  client_->Reset();
+}
+
 // Tests to ensure that we send a BeginMainFrameNotExpectedSoon when expected.
 TEST_F(SchedulerTest, SendBeginMainFrameNotExpectedSoon) {
   SetUpScheduler(EXTERNAL_BFS);
@@ -2759,7 +2809,8 @@
   // The following BeginImplFrame deadline should SetNeedsBeginFrame(false)
   // and send a SendBeginMainFrameNotExpectedSoon.
   EXPECT_SCOPED(AdvanceFrame());
-  EXPECT_SINGLE_ACTION("WillBeginImplFrame", client_);
+  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 2);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 1, 2);
   EXPECT_TRUE(client_->IsInsideBeginImplFrame());
   client_->Reset();
 
@@ -2784,8 +2835,9 @@
 
   // Next vsync.
   AdvanceFrame();
-  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 2);
-  EXPECT_ACTION("ScheduledActionInvalidateCompositorFrameSink", client_, 1, 2);
+  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 3);
+  EXPECT_ACTION("ScheduledActionInvalidateCompositorFrameSink", client_, 2, 3);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 1, 3);
   EXPECT_FALSE(client_->IsInsideBeginImplFrame());
   client_->Reset();
 
@@ -2803,8 +2855,9 @@
 
   // Next vsync.
   AdvanceFrame();
-  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 2);
-  EXPECT_ACTION("ScheduledActionInvalidateCompositorFrameSink", client_, 1, 2);
+  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 3);
+  EXPECT_ACTION("ScheduledActionInvalidateCompositorFrameSink", client_, 1, 3);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 2, 3);
   EXPECT_FALSE(client_->IsInsideBeginImplFrame());
   client_->Reset();
 
@@ -2817,9 +2870,10 @@
 
   // Idle on next vsync, as the animation has completed.
   AdvanceFrame();
-  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 3);
-  EXPECT_ACTION("RemoveObserver(this)", client_, 1, 3);
-  EXPECT_ACTION("SendBeginMainFrameNotExpectedSoon", client_, 2, 3);
+  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 4);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 1, 4);
+  EXPECT_ACTION("RemoveObserver(this)", client_, 2, 4);
+  EXPECT_ACTION("SendBeginMainFrameNotExpectedSoon", client_, 3, 4);
   EXPECT_FALSE(client_->IsInsideBeginImplFrame());
   client_->Reset();
 }
@@ -2838,9 +2892,10 @@
 
   // Idle on next vsync.
   AdvanceFrame();
-  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 3);
-  EXPECT_ACTION("RemoveObserver(this)", client_, 1, 3);
-  EXPECT_ACTION("SendBeginMainFrameNotExpectedSoon", client_, 2, 3);
+  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 4);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 1, 4);
+  EXPECT_ACTION("RemoveObserver(this)", client_, 2, 4);
+  EXPECT_ACTION("SendBeginMainFrameNotExpectedSoon", client_, 3, 4);
   EXPECT_FALSE(client_->IsInsideBeginImplFrame());
   client_->Reset();
 }
@@ -2860,7 +2915,8 @@
 
   // Next vsync, the first requested frame happens.
   EXPECT_SCOPED(AdvanceFrame());
-  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 1);
+  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 2);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 1, 2);
   EXPECT_TRUE(client_->IsInsideBeginImplFrame());
   client_->Reset();
 
@@ -2869,7 +2925,8 @@
   // Next vsync, the second requested frame happens (the one requested inside
   // the previous frame's begin impl frame step).
   EXPECT_SCOPED(AdvanceFrame());
-  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 1);
+  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 2);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 1, 2);
   EXPECT_TRUE(client_->IsInsideBeginImplFrame());
   client_->Reset();
 
@@ -2930,7 +2987,7 @@
       fake_external_begin_frame_source_->LastAckForObserver(scheduler_.get()));
 
   scheduler_->NotifyReadyToCommit();
-  EXPECT_SINGLE_ACTION("ScheduledActionCommit", client_);
+  EXPECT_ACTION("ScheduledActionCommit", client_, 0, 1);
   client_->Reset();
 
   scheduler_->NotifyReadyToActivate();
@@ -2939,8 +2996,9 @@
 
   // Next vsync.
   args = SendNextBeginFrame();
-  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 2);
-  EXPECT_ACTION("ScheduledActionInvalidateCompositorFrameSink", client_, 1, 2);
+  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 3);
+  EXPECT_ACTION("ScheduledActionInvalidateCompositorFrameSink", client_, 1, 3);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 2, 3);
   EXPECT_FALSE(client_->IsInsideBeginImplFrame());
   client_->Reset();
 
@@ -2964,9 +3022,10 @@
 
   // Idle on next vsync.
   args = SendNextBeginFrame();
-  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 3);
-  EXPECT_ACTION("RemoveObserver(this)", client_, 1, 3);
-  EXPECT_ACTION("SendBeginMainFrameNotExpectedSoon", client_, 2, 3);
+  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 4);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 1, 4);
+  EXPECT_ACTION("RemoveObserver(this)", client_, 2, 4);
+  EXPECT_ACTION("SendBeginMainFrameNotExpectedSoon", client_, 3, 4);
   EXPECT_FALSE(client_->IsInsideBeginImplFrame());
   client_->Reset();
 
@@ -3047,8 +3106,9 @@
 
   // Next vsync.
   EXPECT_SCOPED(AdvanceFrame());
-  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 2);
-  EXPECT_ACTION("ScheduledActionInvalidateCompositorFrameSink", client_, 1, 2);
+  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 3);
+  EXPECT_ACTION("ScheduledActionInvalidateCompositorFrameSink", client_, 1, 3);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 2, 3);
   client_->Reset();
 
   // Android onDraw.
@@ -3073,9 +3133,10 @@
   // Next vsync.
   EXPECT_SCOPED(AdvanceFrame());
   EXPECT_FALSE(scheduler_->PrepareTilesPending());
-  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 3);
-  EXPECT_ACTION("RemoveObserver(this)", client_, 1, 3);
-  EXPECT_ACTION("SendBeginMainFrameNotExpectedSoon", client_, 2, 3);
+  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 4);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 1, 4);
+  EXPECT_ACTION("RemoveObserver(this)", client_, 2, 4);
+  EXPECT_ACTION("SendBeginMainFrameNotExpectedSoon", client_, 3, 4);
   EXPECT_FALSE(scheduler_->begin_frames_expected());
   client_->Reset();
 }
@@ -3090,8 +3151,9 @@
 
   // Next vsync.
   EXPECT_SCOPED(AdvanceFrame());
-  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 2);
-  EXPECT_ACTION("ScheduledActionInvalidateCompositorFrameSink", client_, 1, 2);
+  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 3);
+  EXPECT_ACTION("ScheduledActionInvalidateCompositorFrameSink", client_, 1, 3);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 2, 3);
   client_->Reset();
 
   // Android onDraw.
@@ -3119,8 +3181,9 @@
 
   // Next vsync.
   EXPECT_SCOPED(AdvanceFrame());
-  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 2);
-  EXPECT_ACTION("ScheduledActionInvalidateCompositorFrameSink", client_, 1, 2);
+  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 3);
+  EXPECT_ACTION("ScheduledActionInvalidateCompositorFrameSink", client_, 1, 3);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 2, 3);
   client_->Reset();
 
   // Android onDraw.
@@ -3255,7 +3318,8 @@
   scheduler_->SetNeedsImplSideInvalidation();
   client_->Reset();
   EXPECT_SCOPED(AdvanceFrame());
-  EXPECT_SINGLE_ACTION("WillBeginImplFrame", client_);
+  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 2);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 1, 2);
 
   // Deadline.
   client_->Reset();
@@ -3421,7 +3485,8 @@
 
   BeginFrameArgs args = SendNextBeginFrame();
   EXPECT_LT(latest_confirmed_sequence_number, args.sequence_number);
-  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 1);
+  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 2);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 1, 2);
   EXPECT_TRUE(client_->IsInsideBeginImplFrame());
   EXPECT_TRUE(scheduler_->begin_frames_expected());
   client_->Reset();
@@ -3447,7 +3512,8 @@
 
   args = SendNextBeginFrame();
   EXPECT_LT(latest_confirmed_sequence_number, args.sequence_number);
-  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 1);
+  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 2);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 1, 2);
   EXPECT_TRUE(client_->IsInsideBeginImplFrame());
   EXPECT_TRUE(scheduler_->begin_frames_expected());
   client_->Reset();
@@ -3482,7 +3548,8 @@
   client_->Reset();
 
   BeginFrameArgs args = SendNextBeginFrame();
-  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 1);
+  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 2);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 1, 2);
   EXPECT_TRUE(client_->IsInsideBeginImplFrame());
   EXPECT_TRUE(scheduler_->begin_frames_expected());
   client_->Reset();
@@ -3529,7 +3596,8 @@
   client_->Reset();
 
   SendNextBeginFrame();
-  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 1);
+  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 2);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 1, 2);
   EXPECT_TRUE(client_->IsInsideBeginImplFrame());
   // Until tiles were prepared, further proactive BeginFrames are expected.
   EXPECT_TRUE(scheduler_->begin_frames_expected());
@@ -3573,7 +3641,8 @@
 
   // First BeginFrame is handled by StateMachine.
   BeginFrameArgs first_args = SendNextBeginFrame();
-  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 1);
+  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 2);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 1, 2);
   EXPECT_TRUE(client_->IsInsideBeginImplFrame());
   // State machine is no longer interested in BeginFrames, but scheduler is
   // still observing the source.
@@ -3654,7 +3723,8 @@
                                                        source_id, 1, now_src());
   fake_external_begin_frame_source_->TestOnBeginFrame(args);
 
-  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 1);
+  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 2);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 1, 2);
   EXPECT_TRUE(client_->IsInsideBeginImplFrame());
   EXPECT_TRUE(scheduler_->begin_frames_expected());
   client_->Reset();
@@ -3714,7 +3784,8 @@
 
   client_->Reset();
   EXPECT_SCOPED(AdvanceFrame());
-  EXPECT_SINGLE_ACTION("WillBeginImplFrame", client_);
+  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 2);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 1, 2);
 
   // Do not run pending deadline but send a new begin frame. The begin frame is
   // saved and run when the previous frame is over.
@@ -3728,7 +3799,8 @@
   // The saved begin frame is posted as a task.
   client_->Reset();
   task_runner_->RunPendingTasks();
-  EXPECT_SINGLE_ACTION("WillBeginImplFrame", client_);
+  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 2);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 1, 2);
 }
 
 TEST_F(SchedulerTest, IncomingBeginFrameReplacesSavedBeginFrame) {
@@ -3738,7 +3810,8 @@
 
   client_->Reset();
   EXPECT_SCOPED(AdvanceFrame());
-  EXPECT_SINGLE_ACTION("WillBeginImplFrame", client_);
+  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 2);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 1, 2);
 
   // Send two BeginFrames while deadline is pending.
   client_->Reset();
@@ -3754,7 +3827,8 @@
   // Only the last BeginFrame runs.
   client_->Reset();
   task_runner_->RunPendingTasks();
-  EXPECT_SINGLE_ACTION("WillBeginImplFrame", client_);
+  EXPECT_ACTION("WillBeginImplFrame", client_, 0, 2);
+  EXPECT_ACTION("ScheduledActionBeginMainFrameNotExpectedUntil", client_, 1, 2);
 
   client_->Reset();
   task_runner_->RunPendingTasks();
diff --git a/cc/test/layer_tree_test.cc b/cc/test/layer_tree_test.cc
index b07d78d..999be132 100644
--- a/cc/test/layer_tree_test.cc
+++ b/cc/test/layer_tree_test.cc
@@ -364,6 +364,7 @@
   void BeginMainFrameNotExpectedSoon() override {
     test_hooks_->BeginMainFrameNotExpectedSoon();
   }
+  void BeginMainFrameNotExpectedUntil(base::TimeTicks time) override {}
 
   bool IsForSubframe() override { return false; }
 
diff --git a/cc/test/stub_layer_tree_host_client.h b/cc/test/stub_layer_tree_host_client.h
index 065f692..948e5b2 100644
--- a/cc/test/stub_layer_tree_host_client.h
+++ b/cc/test/stub_layer_tree_host_client.h
@@ -18,6 +18,7 @@
   void DidBeginMainFrame() override {}
   void BeginMainFrame(const BeginFrameArgs& args) override {}
   void BeginMainFrameNotExpectedSoon() override {}
+  void BeginMainFrameNotExpectedUntil(base::TimeTicks time) override {}
   void UpdateLayerTreeHost() override {}
   void ApplyViewportDeltas(const gfx::Vector2dF& inner_delta,
                            const gfx::Vector2dF& outer_delta,
diff --git a/cc/trees/layer_tree_host.cc b/cc/trees/layer_tree_host.cc
index 11a354a..7abf735a 100644
--- a/cc/trees/layer_tree_host.cc
+++ b/cc/trees/layer_tree_host.cc
@@ -247,6 +247,10 @@
   client_->BeginMainFrameNotExpectedSoon();
 }
 
+void LayerTreeHost::BeginMainFrameNotExpectedUntil(base::TimeTicks time) {
+  client_->BeginMainFrameNotExpectedUntil(time);
+}
+
 void LayerTreeHost::BeginMainFrame(const BeginFrameArgs& args) {
   client_->BeginMainFrame(args);
 }
diff --git a/cc/trees/layer_tree_host.h b/cc/trees/layer_tree_host.h
index 9ecdc77..3919895 100644
--- a/cc/trees/layer_tree_host.h
+++ b/cc/trees/layer_tree_host.h
@@ -387,6 +387,7 @@
   void DidBeginMainFrame();
   void BeginMainFrame(const BeginFrameArgs& args);
   void BeginMainFrameNotExpectedSoon();
+  void BeginMainFrameNotExpectedUntil(base::TimeTicks time);
   void AnimateLayers(base::TimeTicks monotonic_frame_begin_time);
   void RequestMainFrameUpdate();
   void FinishCommitOnImplThread(LayerTreeHostImpl* host_impl);
diff --git a/cc/trees/layer_tree_host_client.h b/cc/trees/layer_tree_host_client.h
index 84416d6..f54a69b0 100644
--- a/cc/trees/layer_tree_host_client.h
+++ b/cc/trees/layer_tree_host_client.h
@@ -8,6 +8,7 @@
 #include <memory>
 
 #include "base/memory/ref_counted.h"
+#include "base/time/time.h"
 
 namespace gfx {
 class Vector2dF;
@@ -24,6 +25,7 @@
   // mode, this corresponds to DidCommit().
   virtual void BeginMainFrame(const BeginFrameArgs& args) = 0;
   virtual void BeginMainFrameNotExpectedSoon() = 0;
+  virtual void BeginMainFrameNotExpectedUntil(base::TimeTicks time) = 0;
   virtual void DidBeginMainFrame() = 0;
   // A LayerTreeHost is bound to a LayerTreeHostClient. Visual frame-based
   // updates to the state of the LayerTreeHost are expected to happen only in
diff --git a/cc/trees/proxy_impl.cc b/cc/trees/proxy_impl.cc
index 48d1902..3d3b4a6 100644
--- a/cc/trees/proxy_impl.cc
+++ b/cc/trees/proxy_impl.cc
@@ -612,6 +612,14 @@
                                 proxy_main_weak_ptr_));
 }
 
+void ProxyImpl::ScheduledActionBeginMainFrameNotExpectedUntil(
+    base::TimeTicks time) {
+  DCHECK(IsImplThread());
+  MainThreadTaskRunner()->PostTask(
+      FROM_HERE, base::Bind(&ProxyMain::BeginMainFrameNotExpectedUntil,
+                            proxy_main_weak_ptr_, time));
+}
+
 DrawResult ProxyImpl::DrawInternal(bool forced_draw) {
   TRACE_EVENT_SYNTHETIC_DELAY("cc.Draw");
 
diff --git a/cc/trees/proxy_impl.h b/cc/trees/proxy_impl.h
index e80a118..46071cdd 100644
--- a/cc/trees/proxy_impl.h
+++ b/cc/trees/proxy_impl.h
@@ -108,6 +108,8 @@
   void ScheduledActionInvalidateCompositorFrameSink() override;
   void ScheduledActionPerformImplSideInvalidation() override;
   void SendBeginMainFrameNotExpectedSoon() override;
+  void ScheduledActionBeginMainFrameNotExpectedUntil(
+      base::TimeTicks time) override;
 
   DrawResult DrawInternal(bool forced_draw);
 
diff --git a/cc/trees/proxy_main.cc b/cc/trees/proxy_main.cc
index 43f6fc3..8632105c 100644
--- a/cc/trees/proxy_main.cc
+++ b/cc/trees/proxy_main.cc
@@ -75,6 +75,12 @@
   layer_tree_host_->BeginMainFrameNotExpectedSoon();
 }
 
+void ProxyMain::BeginMainFrameNotExpectedUntil(base::TimeTicks time) {
+  TRACE_EVENT0("cc", "ProxyMain::BeginMainFrameNotExpectedUntil");
+  DCHECK(IsMainThread());
+  layer_tree_host_->BeginMainFrameNotExpectedUntil(time);
+}
+
 void ProxyMain::DidCommitAndDrawFrame() {
   DCHECK(IsMainThread());
   layer_tree_host_->DidCommitAndDrawFrame();
diff --git a/cc/trees/proxy_main.h b/cc/trees/proxy_main.h
index 9fb7823d..e4ab354 100644
--- a/cc/trees/proxy_main.h
+++ b/cc/trees/proxy_main.h
@@ -42,6 +42,7 @@
 
   void DidReceiveCompositorFrameAck();
   void BeginMainFrameNotExpectedSoon();
+  void BeginMainFrameNotExpectedUntil(base::TimeTicks time);
   void DidCommitAndDrawFrame();
   void SetAnimationEvents(std::unique_ptr<MutatorEvents> events);
   void DidLoseCompositorFrameSink();
diff --git a/cc/trees/single_thread_proxy.cc b/cc/trees/single_thread_proxy.cc
index 29821e38..777df13f 100644
--- a/cc/trees/single_thread_proxy.cc
+++ b/cc/trees/single_thread_proxy.cc
@@ -632,6 +632,11 @@
   layer_tree_host_->BeginMainFrameNotExpectedSoon();
 }
 
+void SingleThreadProxy::ScheduledActionBeginMainFrameNotExpectedUntil(
+    base::TimeTicks time) {
+  layer_tree_host_->BeginMainFrameNotExpectedUntil(time);
+}
+
 void SingleThreadProxy::BeginMainFrame(const BeginFrameArgs& begin_frame_args) {
   if (scheduler_on_impl_thread_) {
     scheduler_on_impl_thread_->NotifyBeginMainFrameStarted(
diff --git a/cc/trees/single_thread_proxy.h b/cc/trees/single_thread_proxy.h
index 4cbb14a..54e01cfd 100644
--- a/cc/trees/single_thread_proxy.h
+++ b/cc/trees/single_thread_proxy.h
@@ -72,6 +72,8 @@
   void ScheduledActionInvalidateCompositorFrameSink() override;
   void ScheduledActionPerformImplSideInvalidation() override;
   void SendBeginMainFrameNotExpectedSoon() override;
+  void ScheduledActionBeginMainFrameNotExpectedUntil(
+      base::TimeTicks time) override;
 
   // LayerTreeHostImplClient implementation
   void DidLoseCompositorFrameSinkOnImplThread() override;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadNotificationService.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadNotificationService.java
index 8c46a21..eb4121a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadNotificationService.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadNotificationService.java
@@ -46,11 +46,11 @@
 import org.chromium.chrome.browser.init.BrowserParts;
 import org.chromium.chrome.browser.init.ChromeBrowserInitializer;
 import org.chromium.chrome.browser.init.EmptyBrowserParts;
-import org.chromium.chrome.browser.notifications.ChannelDefinitions;
 import org.chromium.chrome.browser.notifications.ChromeNotificationBuilder;
 import org.chromium.chrome.browser.notifications.NotificationBuilderFactory;
 import org.chromium.chrome.browser.notifications.NotificationConstants;
 import org.chromium.chrome.browser.notifications.NotificationUmaTracker;
+import org.chromium.chrome.browser.notifications.channels.ChannelDefinitions;
 import org.chromium.chrome.browser.offlinepages.downloads.OfflinePageDownloadBridge;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.util.IntentUtils;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/incognito/IncognitoNotificationManager.java b/chrome/android/java/src/org/chromium/chrome/browser/incognito/IncognitoNotificationManager.java
index db8c414e..3ed7c9e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/incognito/IncognitoNotificationManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/incognito/IncognitoNotificationManager.java
@@ -10,11 +10,11 @@
 
 import org.chromium.base.ContextUtils;
 import org.chromium.chrome.R;
-import org.chromium.chrome.browser.notifications.ChannelDefinitions;
 import org.chromium.chrome.browser.notifications.ChromeNotificationBuilder;
 import org.chromium.chrome.browser.notifications.NotificationBuilderFactory;
 import org.chromium.chrome.browser.notifications.NotificationConstants;
 import org.chromium.chrome.browser.notifications.NotificationUmaTracker;
+import org.chromium.chrome.browser.notifications.channels.ChannelDefinitions;
 
 /**
  * Manages the notification indicating that there are incognito tabs opened in Document mode.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java
index dddd8ef..903ce8c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java
@@ -53,7 +53,7 @@
 import org.chromium.chrome.browser.metrics.UmaUtils;
 import org.chromium.chrome.browser.multiwindow.MultiWindowUtils;
 import org.chromium.chrome.browser.net.spdyproxy.DataReductionProxySettings;
-import org.chromium.chrome.browser.notifications.ChannelsUpdater;
+import org.chromium.chrome.browser.notifications.channels.ChannelsUpdater;
 import org.chromium.chrome.browser.ntp.NewTabPage;
 import org.chromium.chrome.browser.offlinepages.OfflinePageUtils;
 import org.chromium.chrome.browser.partnerbookmarks.PartnerBookmarksShim;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/MediaCaptureNotificationService.java b/chrome/android/java/src/org/chromium/chrome/browser/media/MediaCaptureNotificationService.java
index dec18c4..a7507873 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/MediaCaptureNotificationService.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/media/MediaCaptureNotificationService.java
@@ -17,10 +17,10 @@
 import org.chromium.base.ContextUtils;
 import org.chromium.base.Log;
 import org.chromium.chrome.R;
-import org.chromium.chrome.browser.notifications.ChannelDefinitions;
 import org.chromium.chrome.browser.notifications.ChromeNotificationBuilder;
 import org.chromium.chrome.browser.notifications.NotificationBuilderFactory;
 import org.chromium.chrome.browser.notifications.NotificationUmaTracker;
+import org.chromium.chrome.browser.notifications.channels.ChannelDefinitions;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabWebContentsDelegateAndroid;
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaNotificationManager.java b/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaNotificationManager.java
index 029c636..3eca3e9 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaNotificationManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaNotificationManager.java
@@ -42,11 +42,11 @@
 import org.chromium.blink.mojom.MediaSessionAction;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.AppHooks;
-import org.chromium.chrome.browser.notifications.ChannelDefinitions;
 import org.chromium.chrome.browser.notifications.ChromeNotificationBuilder;
 import org.chromium.chrome.browser.notifications.NotificationBuilderFactory;
 import org.chromium.chrome.browser.notifications.NotificationConstants;
 import org.chromium.chrome.browser.notifications.NotificationUmaTracker;
+import org.chromium.chrome.browser.notifications.channels.ChannelDefinitions;
 import org.chromium.content_public.common.MediaMetadata;
 
 import java.util.ArrayList;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/notifications/CustomNotificationBuilder.java b/chrome/android/java/src/org/chromium/chrome/browser/notifications/CustomNotificationBuilder.java
index e407791..a1f6265 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/notifications/CustomNotificationBuilder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/notifications/CustomNotificationBuilder.java
@@ -24,6 +24,7 @@
 import org.chromium.base.VisibleForTesting;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.notifications.channels.ChannelDefinitions;
 import org.chromium.ui.base.LocalizationUtils;
 
 import java.util.Date;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationBuilderFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationBuilderFactory.java
index 43f90b62..c6794917 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationBuilderFactory.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationBuilderFactory.java
@@ -10,6 +10,8 @@
 
 import org.chromium.base.BuildInfo;
 import org.chromium.base.ContextUtils;
+import org.chromium.chrome.browser.notifications.channels.ChannelDefinitions;
+import org.chromium.chrome.browser.notifications.channels.ChannelsInitializer;
 
 /**
  * Factory which supplies the appropriate type of notification builder based on Android version.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationBuilderForO.java b/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationBuilderForO.java
index 6a4ce4cc..f4423670 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationBuilderForO.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationBuilderForO.java
@@ -10,6 +10,8 @@
 
 import org.chromium.base.BuildInfo;
 import org.chromium.base.Log;
+import org.chromium.chrome.browser.notifications.channels.ChannelDefinitions;
+import org.chromium.chrome.browser.notifications.channels.ChannelsInitializer;
 
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationManagerProxy.java b/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationManagerProxy.java
index 150862b..5070fedc 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationManagerProxy.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationManagerProxy.java
@@ -6,6 +6,8 @@
 
 import android.app.Notification;
 
+import org.chromium.chrome.browser.notifications.channels.ChannelDefinitions;
+
 import java.util.List;
 
 /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationManagerProxyImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationManagerProxyImpl.java
index f255ca1..f9f8e5a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationManagerProxyImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationManagerProxyImpl.java
@@ -10,6 +10,7 @@
 import org.chromium.base.BuildInfo;
 import org.chromium.base.ContextUtils;
 import org.chromium.base.Log;
+import org.chromium.chrome.browser.notifications.channels.ChannelDefinitions;
 
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationPlatformBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationPlatformBridge.java
index 2eb51fec..bbb20d278 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationPlatformBridge.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationPlatformBridge.java
@@ -31,6 +31,7 @@
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.init.ChromeBrowserInitializer;
+import org.chromium.chrome.browser.notifications.channels.ChannelDefinitions;
 import org.chromium.chrome.browser.preferences.PrefServiceBridge;
 import org.chromium.chrome.browser.preferences.Preferences;
 import org.chromium.chrome.browser.preferences.PreferencesLauncher;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationUmaTracker.java b/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationUmaTracker.java
index 312d43d..06b8dbb 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationUmaTracker.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationUmaTracker.java
@@ -15,6 +15,7 @@
 import org.chromium.base.Log;
 import org.chromium.base.library_loader.LibraryLoader;
 import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.chrome.browser.notifications.channels.ChannelDefinitions;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/notifications/StandardNotificationBuilder.java b/chrome/android/java/src/org/chromium/chrome/browser/notifications/StandardNotificationBuilder.java
index 702decb3..171b7b5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/notifications/StandardNotificationBuilder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/notifications/StandardNotificationBuilder.java
@@ -8,6 +8,8 @@
 import android.content.Context;
 import android.os.Build;
 
+import org.chromium.chrome.browser.notifications.channels.ChannelDefinitions;
+
 /**
  * Builds a notification using the standard Notification.BigTextStyle layout.
  */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/notifications/ChannelDefinitions.java b/chrome/android/java/src/org/chromium/chrome/browser/notifications/channels/ChannelDefinitions.java
similarity index 96%
rename from chrome/android/java/src/org/chromium/chrome/browser/notifications/ChannelDefinitions.java
rename to chrome/android/java/src/org/chromium/chrome/browser/notifications/channels/ChannelDefinitions.java
index 8e66a2e..81976b4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/notifications/ChannelDefinitions.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/notifications/channels/ChannelDefinitions.java
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.chrome.browser.notifications;
+package org.chromium.chrome.browser.notifications.channels;
 
 import android.annotation.TargetApi;
 import android.app.NotificationManager;
@@ -137,10 +137,10 @@
     public static class Channel {
         @ChannelId
         public final String mId;
-        final int mNameResId;
-        final int mImportance;
+        public final int mNameResId;
+        public final int mImportance;
         @ChannelGroupId
-        final String mGroupId;
+        public final String mGroupId;
 
         Channel(@ChannelId String id, int nameResId, int importance,
                 @ChannelGroupId String groupId) {
@@ -156,8 +156,8 @@
      */
     public static class ChannelGroup {
         @ChannelGroupId
-        final String mId;
-        final int mNameResId;
+        public final String mId;
+        public final int mNameResId;
 
         ChannelGroup(@ChannelGroupId String id, int nameResId) {
             this.mId = id;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/notifications/ChannelsInitializer.java b/chrome/android/java/src/org/chromium/chrome/browser/notifications/channels/ChannelsInitializer.java
similarity index 94%
rename from chrome/android/java/src/org/chromium/chrome/browser/notifications/ChannelsInitializer.java
rename to chrome/android/java/src/org/chromium/chrome/browser/notifications/channels/ChannelsInitializer.java
index 299189c..d7a9d36a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/notifications/ChannelsInitializer.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/notifications/channels/ChannelsInitializer.java
@@ -2,13 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.chrome.browser.notifications;
+package org.chromium.chrome.browser.notifications.channels;
+
+import org.chromium.chrome.browser.notifications.NotificationManagerProxy;
 
 /**
  * Initializes our notification channels.
  */
 public class ChannelsInitializer {
-
     private final NotificationManagerProxy mNotificationManager;
     private final ChannelDefinitions mChannelDefinitions;
 
@@ -57,5 +58,4 @@
                 mChannelDefinitions.getChannelGroupFromId(channel));
         mNotificationManager.createNotificationChannel(channel);
     }
-
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/notifications/ChannelsUpdater.java b/chrome/android/java/src/org/chromium/chrome/browser/notifications/channels/ChannelsUpdater.java
similarity index 95%
rename from chrome/android/java/src/org/chromium/chrome/browser/notifications/ChannelsUpdater.java
rename to chrome/android/java/src/org/chromium/chrome/browser/notifications/channels/ChannelsUpdater.java
index 8a7a1a95..cbec962 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/notifications/ChannelsUpdater.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/notifications/channels/ChannelsUpdater.java
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.chrome.browser.notifications;
+package org.chromium.chrome.browser.notifications.channels;
 
 import android.app.NotificationManager;
 import android.content.Context;
@@ -11,6 +11,7 @@
 import org.chromium.base.BuildInfo;
 import org.chromium.base.ContextUtils;
 import org.chromium.base.VisibleForTesting;
+import org.chromium.chrome.browser.notifications.NotificationManagerProxyImpl;
 
 /**
  * Contains helper methods for checking if we should update channels and updating them if so.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/ContentSuggestionsNotificationHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/ContentSuggestionsNotificationHelper.java
index 7f95393a..5c4e7bd 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/ContentSuggestionsNotificationHelper.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/ContentSuggestionsNotificationHelper.java
@@ -24,10 +24,10 @@
 import org.chromium.chrome.browser.IntentHandler;
 import org.chromium.chrome.browser.ShortcutHelper;
 import org.chromium.chrome.browser.document.ChromeLauncherActivity;
-import org.chromium.chrome.browser.notifications.ChannelDefinitions;
 import org.chromium.chrome.browser.notifications.ChromeNotificationBuilder;
 import org.chromium.chrome.browser.notifications.NotificationBuilderFactory;
 import org.chromium.chrome.browser.notifications.NotificationUmaTracker;
+import org.chromium.chrome.browser.notifications.channels.ChannelDefinitions;
 import org.chromium.chrome.browser.ntp.snippets.ContentSuggestionsNotificationAction;
 import org.chromium.chrome.browser.ntp.snippets.ContentSuggestionsNotificationOptOut;
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/physicalweb/PhysicalWebBroadcastService.java b/chrome/android/java/src/org/chromium/chrome/browser/physicalweb/PhysicalWebBroadcastService.java
index bf83d6a..6382d933 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/physicalweb/PhysicalWebBroadcastService.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/physicalweb/PhysicalWebBroadcastService.java
@@ -20,13 +20,13 @@
 import org.chromium.base.ContextUtils;
 import org.chromium.base.Log;
 import org.chromium.chrome.R;
-import org.chromium.chrome.browser.notifications.ChannelDefinitions;
 import org.chromium.chrome.browser.notifications.ChromeNotificationBuilder;
 import org.chromium.chrome.browser.notifications.NotificationBuilderFactory;
 import org.chromium.chrome.browser.notifications.NotificationConstants;
 import org.chromium.chrome.browser.notifications.NotificationManagerProxy;
 import org.chromium.chrome.browser.notifications.NotificationManagerProxyImpl;
 import org.chromium.chrome.browser.notifications.NotificationUmaTracker;
+import org.chromium.chrome.browser.notifications.channels.ChannelDefinitions;
 import org.chromium.chrome.browser.util.IntentUtils;
 
 /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/sync/SyncNotificationController.java b/chrome/android/java/src/org/chromium/chrome/browser/sync/SyncNotificationController.java
index ea8a1d5..5b58733 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/sync/SyncNotificationController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/sync/SyncNotificationController.java
@@ -16,13 +16,13 @@
 
 import org.chromium.base.ThreadUtils;
 import org.chromium.chrome.R;
-import org.chromium.chrome.browser.notifications.ChannelDefinitions;
 import org.chromium.chrome.browser.notifications.ChromeNotificationBuilder;
 import org.chromium.chrome.browser.notifications.NotificationBuilderFactory;
 import org.chromium.chrome.browser.notifications.NotificationConstants;
 import org.chromium.chrome.browser.notifications.NotificationManagerProxy;
 import org.chromium.chrome.browser.notifications.NotificationManagerProxyImpl;
 import org.chromium.chrome.browser.notifications.NotificationUmaTracker;
+import org.chromium.chrome.browser.notifications.channels.ChannelDefinitions;
 import org.chromium.chrome.browser.preferences.PreferencesLauncher;
 import org.chromium.components.sync.AndroidSyncSettings;
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/upgrade/PackageReplacedBroadcastReceiver.java b/chrome/android/java/src/org/chromium/chrome/browser/upgrade/PackageReplacedBroadcastReceiver.java
index 872c4bb..f8126f7 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/upgrade/PackageReplacedBroadcastReceiver.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/upgrade/PackageReplacedBroadcastReceiver.java
@@ -10,7 +10,7 @@
 import android.os.AsyncTask;
 
 import org.chromium.base.BuildInfo;
-import org.chromium.chrome.browser.notifications.ChannelsUpdater;
+import org.chromium.chrome.browser.notifications.channels.ChannelsUpdater;
 
 /**
  * Triggered when Chrome's package is replaced (e.g. when it is upgraded).
diff --git a/chrome/android/java_sources.gni b/chrome/android/java_sources.gni
index 918e0ef5..a9deeb4 100644
--- a/chrome/android/java_sources.gni
+++ b/chrome/android/java_sources.gni
@@ -574,9 +574,6 @@
   "java/src/org/chromium/chrome/browser/nfc/BeamController.java",
   "java/src/org/chromium/chrome/browser/nfc/BeamProvider.java",
   "java/src/org/chromium/chrome/browser/notifications/ActionInfo.java",
-  "java/src/org/chromium/chrome/browser/notifications/ChannelDefinitions.java",
-  "java/src/org/chromium/chrome/browser/notifications/ChannelsInitializer.java",
-  "java/src/org/chromium/chrome/browser/notifications/ChannelsUpdater.java",
   "java/src/org/chromium/chrome/browser/notifications/ChromeNotificationBuilder.java",
   "java/src/org/chromium/chrome/browser/notifications/CustomNotificationBuilder.java",
   "java/src/org/chromium/chrome/browser/notifications/NotificationBuilder.java",
@@ -594,6 +591,9 @@
   "java/src/org/chromium/chrome/browser/notifications/NotificationUmaTracker.java",
   "java/src/org/chromium/chrome/browser/notifications/StandardNotificationBuilder.java",
   "java/src/org/chromium/chrome/browser/notifications/WebApkNotificationClient.java",
+  "java/src/org/chromium/chrome/browser/notifications/channels/ChannelDefinitions.java",
+  "java/src/org/chromium/chrome/browser/notifications/channels/ChannelsInitializer.java",
+  "java/src/org/chromium/chrome/browser/notifications/channels/ChannelsUpdater.java",
   "java/src/org/chromium/chrome/browser/ntp/ChromeHomeIncognitoNewTabPage.java",
   "java/src/org/chromium/chrome/browser/ntp/ChromeHomeNewTabPage.java",
   "java/src/org/chromium/chrome/browser/ntp/ChromeHomeNewTabPageBase.java",
@@ -1695,10 +1695,10 @@
   "junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationTestTabHolder.java",
   "junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationTitleUpdatedTest.java",
   "junit/src/org/chromium/chrome/browser/metrics/VariationsSessionTest.java",
-  "junit/src/org/chromium/chrome/browser/notifications/ChannelDefinitionsTest.java",
-  "junit/src/org/chromium/chrome/browser/notifications/ChannelsInitializerTest.java",
-  "junit/src/org/chromium/chrome/browser/notifications/ChannelsUpdaterTest.java",
   "junit/src/org/chromium/chrome/browser/notifications/NotificationPlatformBridgeUnitTest.java",
+  "junit/src/org/chromium/chrome/browser/notifications/channels/ChannelDefinitionsTest.java",
+  "junit/src/org/chromium/chrome/browser/notifications/channels/ChannelsInitializerTest.java",
+  "junit/src/org/chromium/chrome/browser/notifications/channels/ChannelsUpdaterTest.java",
   "junit/src/org/chromium/chrome/browser/ntp/NativePageFactoryTest.java",
   "junit/src/org/chromium/chrome/browser/ntp/TitleUtilTest.java",
   "junit/src/org/chromium/chrome/browser/ntp/cards/ContentSuggestionsUnitTestUtils.java",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java
index 476fc94..83646723 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java
@@ -1477,6 +1477,7 @@
     @SmallTest
     @RetryOnFailure
     @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE)
+    @CommandLineFlags.Add("enable-features=" + ChromeFeatureList.CCT_BACKGROUND_TAB)
     public void testPostMessageThroughHiddenTabWithRequestBeforeMayLaunchUrl()
             throws InterruptedException {
         sendPostMessageDuringHiddenTabTransition(BEFORE_MAY_LAUNCH_URL);
@@ -1489,6 +1490,7 @@
     @SmallTest
     @RetryOnFailure
     @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE)
+    @CommandLineFlags.Add("enable-features=" + ChromeFeatureList.CCT_BACKGROUND_TAB)
     public void testPostMessageThroughHiddenTabWithRequestBeforeIntent()
             throws InterruptedException {
         sendPostMessageDuringHiddenTabTransition(BEFORE_INTENT);
@@ -1501,11 +1503,13 @@
     @SmallTest
     @RetryOnFailure
     @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE)
+    @CommandLineFlags.Add("enable-features=" + ChromeFeatureList.CCT_BACKGROUND_TAB)
     public void testPostMessageThroughHiddenTabWithRequestAfterIntent()
             throws InterruptedException {
         sendPostMessageDuringHiddenTabTransition(AFTER_INTENT);
     }
 
+    @CommandLineFlags.Add("enable-features=" + ChromeFeatureList.CCT_BACKGROUND_TAB)
     private void sendPostMessageDuringHiddenTabTransition(int requestTime)
             throws InterruptedException {
         sendPostMessageDuringSpeculationTransition(
@@ -1788,6 +1792,7 @@
     @SmallTest
     @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE)
     @RetryOnFailure
+    @CommandLineFlags.Add("enable-features=" + ChromeFeatureList.CCT_BACKGROUND_TAB)
     public void testHiddenTabAndChangingFragmentIgnoreFragments() throws Exception {
         startHiddenTabAndChangeFragment(true, true);
     }
@@ -1796,6 +1801,7 @@
     @SmallTest
     @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE)
     @RetryOnFailure
+    @CommandLineFlags.Add("enable-features=" + ChromeFeatureList.CCT_BACKGROUND_TAB)
     public void testHiddenTabAndChangingFragmentDontIgnoreFragments() throws Exception {
         startHiddenTabAndChangeFragment(false, true);
     }
@@ -1804,6 +1810,7 @@
     @SmallTest
     @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE)
     @RetryOnFailure
+    @CommandLineFlags.Add("enable-features=" + ChromeFeatureList.CCT_BACKGROUND_TAB)
     public void testHiddenTabAndChangingFragmentDontWait() throws Exception {
         startHiddenTabAndChangeFragment(true, false);
     }
@@ -1812,6 +1819,7 @@
     @SmallTest
     @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE)
     @RetryOnFailure
+    @CommandLineFlags.Add("enable-features=" + ChromeFeatureList.CCT_BACKGROUND_TAB)
     public void testHiddenTabAndChangingFragmentDontWaitDrop() throws Exception {
         startHiddenTabAndChangeFragment(false, false);
     }
@@ -1900,6 +1908,7 @@
     @SmallTest
     @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE)
     @RetryOnFailure
+    @CommandLineFlags.Add("enable-features=" + ChromeFeatureList.CCT_BACKGROUND_TAB)
     public void testHiddenTabCorrectUrl() throws Exception {
         testSpeculateCorrectUrl(CustomTabsConnection.SpeculationParams.HIDDEN_TAB);
     }
@@ -2105,6 +2114,7 @@
     @SmallTest
     @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE)
     @RetryOnFailure
+    @CommandLineFlags.Add("enable-features=" + ChromeFeatureList.CCT_BACKGROUND_TAB)
     public void testHiddenTabWithReferrer() throws Exception {
         testSpeculatingWithReferrer(CustomTabsConnection.SpeculationParams.HIDDEN_TAB);
     }
@@ -2143,6 +2153,7 @@
     @SmallTest
     @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE)
     @RetryOnFailure
+    @CommandLineFlags.Add("enable-features=" + ChromeFeatureList.CCT_BACKGROUND_TAB)
     public void testHiddenTabWithMismatchedReferrers() throws Exception {
         testSpeculatingWithMismatchedReferrers(CustomTabsConnection.SpeculationParams.HIDDEN_TAB);
     }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationManagerServiceLifecycleTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationManagerServiceLifecycleTest.java
index 612b579..52486de 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationManagerServiceLifecycleTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationManagerServiceLifecycleTest.java
@@ -32,8 +32,8 @@
 
 import org.chromium.base.BaseChromiumApplication;
 import org.chromium.chrome.browser.media.ui.MediaNotificationManager.ListenerService;
-import org.chromium.chrome.browser.notifications.ChannelDefinitions;
 import org.chromium.chrome.browser.notifications.NotificationUmaTracker;
+import org.chromium.chrome.browser.notifications.channels.ChannelDefinitions;
 import org.chromium.content_public.common.MediaMetadata;
 import org.chromium.testing.local.LocalRobolectricTestRunner;
 
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/notifications/ChannelDefinitionsTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/notifications/channels/ChannelDefinitionsTest.java
similarity index 93%
rename from chrome/android/junit/src/org/chromium/chrome/browser/notifications/ChannelDefinitionsTest.java
rename to chrome/android/junit/src/org/chromium/chrome/browser/notifications/channels/ChannelDefinitionsTest.java
index 26e4098..ff933a0 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/notifications/ChannelDefinitionsTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/notifications/channels/ChannelDefinitionsTest.java
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.chrome.browser.notifications;
+package org.chromium.chrome.browser.notifications.channels;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.everyItem;
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/notifications/ChannelsInitializerTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/notifications/channels/ChannelsInitializerTest.java
similarity index 99%
rename from chrome/android/junit/src/org/chromium/chrome/browser/notifications/ChannelsInitializerTest.java
rename to chrome/android/junit/src/org/chromium/chrome/browser/notifications/channels/ChannelsInitializerTest.java
index af09641..75fd5f4 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/notifications/ChannelsInitializerTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/notifications/channels/ChannelsInitializerTest.java
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.chrome.browser.notifications;
+package org.chromium.chrome.browser.notifications.channels;
 
 import static org.hamcrest.Matchers.containsInAnyOrder;
 import static org.hamcrest.Matchers.empty;
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/notifications/ChannelsUpdaterTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/notifications/channels/ChannelsUpdaterTest.java
similarity index 98%
rename from chrome/android/junit/src/org/chromium/chrome/browser/notifications/ChannelsUpdaterTest.java
rename to chrome/android/junit/src/org/chromium/chrome/browser/notifications/channels/ChannelsUpdaterTest.java
index 03f85d0..43f7050 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/notifications/ChannelsUpdaterTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/notifications/channels/ChannelsUpdaterTest.java
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.chrome.browser.notifications;
+package org.chromium.chrome.browser.notifications.channels;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.containsInAnyOrder;
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 9b02091c..49c2b8c 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -66,6 +66,7 @@
 #include "components/spellcheck/spellcheck_build_features.h"
 #include "components/ssl_config/ssl_config_switches.h"
 #include "components/strings/grit/components_strings.h"
+#include "components/suggestions/features.h"
 #include "components/sync/driver/sync_driver_switches.h"
 #include "components/tracing/common/tracing_switches.h"
 #include "components/translate/core/browser/translate_infobar_delegate.h"
@@ -2832,6 +2833,11 @@
      SINGLE_VALUE_TYPE(switches::kShowCertLink)},
 #endif
 
+    {"use-suggestions-even-if-few",
+     flag_descriptions::kUseSuggestionsEvenIfFewFeatureName,
+     flag_descriptions::kUseSuggestionsEvenIfFewFeatureDescription, kOsAll,
+     FEATURE_VALUE_TYPE(suggestions::kUseSuggestionsEvenIfFewFeature)},
+
     // NOTE: Adding new command-line switches requires adding corresponding
     // entries to enum "LoginCustomFlags" in histograms.xml. See note in
     // histograms.xml and don't forget to run AboutFlagsHistogramTest unit test.
diff --git a/chrome/browser/android/chrome_feature_list.cc b/chrome/browser/android/chrome_feature_list.cc
index e4b046c..bec06f4 100644
--- a/chrome/browser/android/chrome_feature_list.cc
+++ b/chrome/browser/android/chrome_feature_list.cc
@@ -115,7 +115,7 @@
                                         base::FEATURE_DISABLED_BY_DEFAULT};
 
 const base::Feature kCCTBackgroundTab{"CCTBackgroundTab",
-                                      base::FEATURE_ENABLED_BY_DEFAULT};
+                                      base::FEATURE_DISABLED_BY_DEFAULT};
 
 const base::Feature kCCTExternalLinkHandling{"CCTExternalLinkHandling",
                                              base::FEATURE_ENABLED_BY_DEFAULT};
diff --git a/chrome/browser/apps/guest_view/web_view_browsertest.cc b/chrome/browser/apps/guest_view/web_view_browsertest.cc
index 6df250b..1ae79da2 100644
--- a/chrome/browser/apps/guest_view/web_view_browsertest.cc
+++ b/chrome/browser/apps/guest_view/web_view_browsertest.cc
@@ -3139,7 +3139,13 @@
   TestHelper("testFindAPI", "web_view/shim", NO_TEST_SERVER);
 }
 
-IN_PROC_BROWSER_TEST_P(WebViewTest, Shim_TestFindAPI_findupdate) {
+// crbug.com/710486
+#if defined(MEMORY_SANITIZER)
+#define MAYBE_Shim_TestFindAPI_findupdate DISABLED_Shim_TestFindAPI_findupdate
+#else
+#define MAYBE_Shim_TestFindAPI_findupdate Shim_TestFindAPI_findupdate
+#endif
+IN_PROC_BROWSER_TEST_P(WebViewTest, MAYBE_Shim_TestFindAPI_findupdate) {
   TestHelper("testFindAPI_findupdate", "web_view/shim", NO_TEST_SERVER);
 }
 
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 48e50e0..5caa2b0a 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -3115,4 +3115,11 @@
 
 const char kEnableHeapProfilingTaskProfiler[] = "Enabled (task mode)";
 
+const char kUseSuggestionsEvenIfFewFeatureName[] =
+    "Disable minimum for server-side tile suggestions on NTP.";
+
+const char kUseSuggestionsEvenIfFewFeatureDescription[] =
+    "Request server-side suggestions even if there are only very few of them "
+    "and use them for tiles on the New Tab Page.";
+
 }  // namespace flag_descriptions
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index f8dd1a1..e144fb0 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -3384,6 +3384,11 @@
 extern const char kEnableHeapProfilingModeNative[];
 extern const char kEnableHeapProfilingTaskProfiler[];
 
+// Name and description of the flag allowing the usage of a small set of
+// server-side suggestions in NTP tiles.
+extern const char kUseSuggestionsEvenIfFewFeatureName[];
+extern const char kUseSuggestionsEvenIfFewFeatureDescription[];
+
 }  // namespace flag_descriptions
 
 #endif  // CHROME_BROWSER_FLAG_DESCRIPTIONS_H_
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/notifications/MockNotificationManagerProxy.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/notifications/MockNotificationManagerProxy.java
index f886479..25e43b42 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/notifications/MockNotificationManagerProxy.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/notifications/MockNotificationManagerProxy.java
@@ -6,8 +6,8 @@
 
 import android.app.Notification;
 
-import org.chromium.chrome.browser.notifications.ChannelDefinitions;
 import org.chromium.chrome.browser.notifications.NotificationManagerProxy;
+import org.chromium.chrome.browser.notifications.channels.ChannelDefinitions;
 
 import java.util.ArrayList;
 import java.util.Iterator;
diff --git a/components/payments/mojom/payment_app.mojom b/components/payments/mojom/payment_app.mojom
index 1b9ee5c7..b43c771 100644
--- a/components/payments/mojom/payment_app.mojom
+++ b/components/payments/mojom/payment_app.mojom
@@ -58,6 +58,8 @@
       => (PaymentHandlerStatus status);
   SetPaymentInstrument(string instrument_key, PaymentInstrument instrument)
       => (PaymentHandlerStatus status);
+  ClearPaymentInstruments()
+      => (PaymentHandlerStatus status);
 };
 
 struct PaymentAppRequest {
diff --git a/components/signin/core/browser/android/javatests/src/org/chromium/components/signin/test/util/FakeAccountManagerDelegate.java b/components/signin/core/browser/android/javatests/src/org/chromium/components/signin/test/util/FakeAccountManagerDelegate.java
index 5a6bd92..331328a 100644
--- a/components/signin/core/browser/android/javatests/src/org/chromium/components/signin/test/util/FakeAccountManagerDelegate.java
+++ b/components/signin/core/browser/android/javatests/src/org/chromium/components/signin/test/util/FakeAccountManagerDelegate.java
@@ -42,10 +42,6 @@
     private final Context mContext;
     private final Set<AccountHolder> mAccounts = new HashSet<>();
 
-    // Tracks the number of in-progress getAccountsByType() tasks so that tests can wait for
-    // their completion.
-    private final ZeroCounter mGetAccountsTaskCounter = new ZeroCounter();
-
     @VisibleForTesting
     public FakeAccountManagerDelegate(Context context, Account... accounts) {
         mContext = context;
@@ -70,12 +66,6 @@
         return validAccounts.toArray(new Account[0]);
     }
 
-    @VisibleForTesting
-    public void waitForGetAccountsTask() throws InterruptedException {
-        // Wait until all tasks are done because we don't know which is being waited for.
-        mGetAccountsTaskCounter.waitUntilZero();
-    }
-
     /**
      * Add an AccountHolder directly.
      *
@@ -177,36 +167,4 @@
         }
         throw new IllegalArgumentException("Can not find AccountHolder for account " + account);
     }
-
-    /**
-     * Simple concurrency helper class for waiting until a resource count becomes zero.
-     */
-    private static class ZeroCounter {
-        private static final int WAIT_TIMEOUT_MS = 10000;
-
-        private final Object mLock = new Object();
-        private int mCount = 0;
-
-        public void increment() {
-            synchronized (mLock) {
-                mCount++;
-            }
-        }
-
-        public void decrement() {
-            synchronized (mLock) {
-                if (--mCount == 0) {
-                    mLock.notifyAll();
-                }
-            }
-        }
-
-        public void waitUntilZero() throws InterruptedException {
-            synchronized (mLock) {
-                while (mCount != 0) {
-                    mLock.wait(WAIT_TIMEOUT_MS);
-                }
-            }
-        }
-    }
 }
diff --git a/components/subresource_filter/core/common/BUILD.gn b/components/subresource_filter/core/common/BUILD.gn
index eed0e8a..6c1ce856 100644
--- a/components/subresource_filter/core/common/BUILD.gn
+++ b/components/subresource_filter/core/common/BUILD.gn
@@ -34,6 +34,8 @@
     "unindexed_ruleset.h",
     "url_pattern.cc",
     "url_pattern.h",
+    "url_pattern_index.cc",
+    "url_pattern_index.h",
   ]
 
   public_deps = [
@@ -57,10 +59,13 @@
     "test_ruleset_creator.h",
     "test_ruleset_utils.cc",
     "test_ruleset_utils.h",
+    "url_rule_test_support.cc",
+    "url_rule_test_support.h",
   ]
   deps = [
     ":common",
     "//base",
+    "//net",
     "//testing/gtest",
     "//third_party/protobuf:protobuf_lite",
   ]
diff --git a/components/subresource_filter/core/common/PRESUBMIT.py b/components/subresource_filter/core/common/PRESUBMIT.py
index bb1e72c..7e05408 100644
--- a/components/subresource_filter/core/common/PRESUBMIT.py
+++ b/components/subresource_filter/core/common/PRESUBMIT.py
@@ -26,7 +26,8 @@
       continue
     basename = input_api.basename(path)
 
-    if basename.endswith('.fbs') or basename == 'indexed_ruleset.cc':
+    if (basename == 'indexed_ruleset.cc' or basename == 'url_pattern_index.cc'
+        or basename.endswith('.fbs')):
       indexed_ruleset_changed = True
     if basename == 'indexed_ruleset.cc':
       for (_, line) in affected_file.ChangedContents():
diff --git a/components/subresource_filter/core/common/document_subresource_filter_unittest.cc b/components/subresource_filter/core/common/document_subresource_filter_unittest.cc
index 7156236..f447177 100644
--- a/components/subresource_filter/core/common/document_subresource_filter_unittest.cc
+++ b/components/subresource_filter/core/common/document_subresource_filter_unittest.cc
@@ -11,6 +11,8 @@
 #include "components/subresource_filter/core/common/test_ruleset_creator.h"
 #include "components/subresource_filter/core/common/test_ruleset_utils.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+#include "url/origin.h"
 
 namespace subresource_filter {
 
@@ -163,7 +165,7 @@
     activation_state.generic_blocking_rules_disabled =
         generic_blocking_rules_disabled;
     return activation_state;
-  };
+  }
 
   const MemoryMappedRuleset* ruleset() { return ruleset_.get(); }
 
diff --git a/components/subresource_filter/core/common/flat/BUILD.gn b/components/subresource_filter/core/common/flat/BUILD.gn
index 195c0cd1..d554b35 100644
--- a/components/subresource_filter/core/common/flat/BUILD.gn
+++ b/components/subresource_filter/core/common/flat/BUILD.gn
@@ -6,6 +6,7 @@
 
 flatbuffer("flatbuffer") {
   sources = [
-    "rules.fbs",
+    "indexed_ruleset.fbs",
+    "url_pattern_index.fbs",
   ]
 }
diff --git a/components/subresource_filter/core/common/flat/indexed_ruleset.fbs b/components/subresource_filter/core/common/flat/indexed_ruleset.fbs
new file mode 100644
index 0000000..9126228
--- /dev/null
+++ b/components/subresource_filter/core/common/flat/indexed_ruleset.fbs
@@ -0,0 +1,22 @@
+// Copyright 2017 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.
+
+// TODO(pkalinnikov): Make including by absolute path work.
+include "url_pattern_index.fbs";
+
+namespace subresource_filter.flat;
+
+// The top-level data structure used to store URL rules.
+table IndexedRuleset {
+  // The index of all blacklist URL rules.
+  blacklist_index : UrlPatternIndex;
+
+  // The index of all whitelist URL rules, except pure deactivation rules.
+  whitelist_index : UrlPatternIndex;
+
+  // The index of all whitelist URL rules with activation options.
+  deactivation_index : UrlPatternIndex;
+}
+
+root_type IndexedRuleset;
diff --git a/components/subresource_filter/core/common/flat/rules.fbs b/components/subresource_filter/core/common/flat/url_pattern_index.fbs
similarity index 88%
rename from components/subresource_filter/core/common/flat/rules.fbs
rename to components/subresource_filter/core/common/flat/url_pattern_index.fbs
index fdeaea9..ace4a05 100644
--- a/components/subresource_filter/core/common/flat/rules.fbs
+++ b/components/subresource_filter/core/common/flat/url_pattern_index.fbs
@@ -1,3 +1,7 @@
+// Copyright 2017 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.
+
 namespace subresource_filter.flat;
 
 // Corresponds to subresource_filter::proto::UrlPatternType.
@@ -89,16 +93,4 @@
   fallback_rules : [UrlRule];
 }
 
-// The top-level data structure used to store URL rules.
-table IndexedRuleset {
-  // The index of all blacklist URL rules.
-  blacklist_index : UrlPatternIndex;
-
-  // The index of all whitelist URL rules, except pure activation rules.
-  whitelist_index : UrlPatternIndex;
-
-  // The index of all whitelist URL rules with activation options.
-  activation_index : UrlPatternIndex;
-}
-
-root_type IndexedRuleset;
+root_type UrlPatternIndex;
diff --git a/components/subresource_filter/core/common/indexed_ruleset.cc b/components/subresource_filter/core/common/indexed_ruleset.cc
index 66d08f4..edbbee8 100644
--- a/components/subresource_filter/core/common/indexed_ruleset.cc
+++ b/components/subresource_filter/core/common/indexed_ruleset.cc
@@ -4,558 +4,56 @@
 
 #include "components/subresource_filter/core/common/indexed_ruleset.h"
 
-#include <algorithm>
-#include <limits>
-#include <string>
-
 #include "base/logging.h"
-#include "base/numerics/safe_conversions.h"
-#include "base/strings/string_util.h"
 #include "components/subresource_filter/core/common/first_party_origin.h"
-#include "components/subresource_filter/core/common/ngram_extractor.h"
-#include "components/subresource_filter/core/common/url_pattern.h"
-#include "third_party/flatbuffers/src/include/flatbuffers/flatbuffers.h"
+#include "url/gurl.h"
+#include "url/origin.h"
 
 namespace subresource_filter {
 
-namespace {
-
-using FlatStringOffset = flatbuffers::Offset<flatbuffers::String>;
-using FlatDomains = flatbuffers::Vector<FlatStringOffset>;
-using FlatDomainsOffset = flatbuffers::Offset<FlatDomains>;
-
-base::StringPiece ToStringPiece(const flatbuffers::String* string) {
-  DCHECK(string);
-  return base::StringPiece(string->c_str(), string->size());
-}
-
-// Performs three-way comparison between two domains. In the total order defined
-// by this predicate, the lengths of domains will be monotonically decreasing.
-int CompareDomains(base::StringPiece lhs_domain, base::StringPiece rhs_domain) {
-  if (lhs_domain.size() != rhs_domain.size())
-    return lhs_domain.size() > rhs_domain.size() ? -1 : 1;
-  return lhs_domain.compare(rhs_domain);
-}
-
-bool HasNoUpperAscii(base::StringPiece string) {
-  return std::none_of(string.begin(), string.end(),
-                      [](char c) { return base::IsAsciiUpper(c); });
-}
-
-// Checks whether a URL |rule| can be converted to its FlatBuffers equivalent,
-// and performs the actual conversion.
-class UrlRuleFlatBufferConverter {
- public:
-  // Creates the converter, and initializes |is_convertible| bit. If
-  // |is_convertible| == true, then all the fields, needed for serializing the
-  // |rule| to FlatBuffer, are initialized (|options|, |anchor_right|, etc.).
-  UrlRuleFlatBufferConverter(const proto::UrlRule& rule) : rule_(rule) {
-    is_convertible_ = InitializeOptions() && InitializeElementTypes() &&
-                      InitializeActivationTypes() && InitializeUrlPattern() &&
-                      IsMeaningful();
-  }
-
-  // Returns whether the |rule| can be converted to its FlatBuffers equivalent.
-  // The conversion is not possible if the rule has attributes not supported by
-  // this client version.
-  bool is_convertible() const { return is_convertible_; }
-
-  bool has_element_types() const { return !!element_types_; }
-  bool has_activation_types() const { return !!activation_types_; }
-
-  // Writes the URL |rule| to the FlatBuffer using the |builder|, and returns
-  // the offset to the serialized rule.
-  flatbuffers::Offset<flat::UrlRule> SerializeConvertedRule(
-      flatbuffers::FlatBufferBuilder* builder) const {
-    DCHECK(is_convertible());
-
-    FlatDomainsOffset domains_included_offset;
-    FlatDomainsOffset domains_excluded_offset;
-    if (rule_.domains_size()) {
-      // TODO(pkalinnikov): Consider sharing the vectors between rules.
-      std::vector<FlatStringOffset> domains_included;
-      std::vector<FlatStringOffset> domains_excluded;
-      // Reserve only for |domains_included| because it is expected to be the
-      // one used more frequently.
-      domains_included.reserve(rule_.domains_size());
-
-      for (const auto& domain_list_item : rule_.domains()) {
-        // Note: The |domain| can have non-ASCII UTF-8 characters, but
-        // ToLowerASCII leaves these intact.
-        // TODO(pkalinnikov): Convert non-ASCII characters to lower case too.
-        // TODO(pkalinnikov): Possibly convert Punycode to IDN here or directly
-        // assume this is done in the proto::UrlRule.
-        const std::string& domain = domain_list_item.domain();
-        auto offset = builder->CreateSharedString(
-            HasNoUpperAscii(domain) ? domain : base::ToLowerASCII(domain));
-
-        if (domain_list_item.exclude())
-          domains_excluded.push_back(offset);
-        else
-          domains_included.push_back(offset);
-      }
-
-      // The comparator ensuring the domains order necessary for fast matching.
-      auto precedes = [&builder](FlatStringOffset lhs, FlatStringOffset rhs) {
-        return CompareDomains(ToStringPiece(flatbuffers::GetTemporaryPointer(
-                                  *builder, lhs)),
-                              ToStringPiece(flatbuffers::GetTemporaryPointer(
-                                  *builder, rhs))) < 0;
-      };
-
-      // The domains are stored in sorted order to support fast matching.
-      if (!domains_included.empty()) {
-        // TODO(pkalinnikov): Don't sort if it is already sorted offline.
-        std::sort(domains_included.begin(), domains_included.end(), precedes);
-        domains_included_offset = builder->CreateVector(domains_included);
-      }
-      if (!domains_excluded.empty()) {
-        std::sort(domains_excluded.begin(), domains_excluded.end(), precedes);
-        domains_excluded_offset = builder->CreateVector(domains_excluded);
-      }
-    }
-
-    auto url_pattern_offset = builder->CreateString(rule_.url_pattern());
-
-    return flat::CreateUrlRule(
-        *builder, options_, element_types_, activation_types_,
-        url_pattern_type_, anchor_left_, anchor_right_, domains_included_offset,
-        domains_excluded_offset, url_pattern_offset);
-  }
-
- private:
-  static bool ConvertAnchorType(proto::AnchorType anchor_type,
-                                flat::AnchorType* result) {
-    switch (anchor_type) {
-      case proto::ANCHOR_TYPE_NONE:
-        *result = flat::AnchorType_NONE;
-        break;
-      case proto::ANCHOR_TYPE_BOUNDARY:
-        *result = flat::AnchorType_BOUNDARY;
-        break;
-      case proto::ANCHOR_TYPE_SUBDOMAIN:
-        *result = flat::AnchorType_SUBDOMAIN;
-        break;
-      default:
-        return false;  // Unsupported anchor type.
-    }
-    return true;
-  }
-
-  bool InitializeOptions() {
-    if (rule_.semantics() == proto::RULE_SEMANTICS_WHITELIST) {
-      options_ |= flat::OptionFlag_IS_WHITELIST;
-    } else if (rule_.semantics() != proto::RULE_SEMANTICS_BLACKLIST) {
-      return false;  // Unsupported semantics.
-    }
-
-    switch (rule_.source_type()) {
-      case proto::SOURCE_TYPE_ANY:
-        options_ |= flat::OptionFlag_APPLIES_TO_THIRD_PARTY;
-      // Note: fall through here intentionally.
-      case proto::SOURCE_TYPE_FIRST_PARTY:
-        options_ |= flat::OptionFlag_APPLIES_TO_FIRST_PARTY;
-        break;
-      case proto::SOURCE_TYPE_THIRD_PARTY:
-        options_ |= flat::OptionFlag_APPLIES_TO_THIRD_PARTY;
-        break;
-
-      default:
-        return false;  // Unsupported source type.
-    }
-
-    if (rule_.match_case())
-      options_ |= flat::OptionFlag_IS_MATCH_CASE;
-
-    return true;
-  }
-
-  bool InitializeElementTypes() {
-    static_assert(
-        proto::ELEMENT_TYPE_ALL <= std::numeric_limits<uint16_t>::max(),
-        "Element types can not be stored in uint16_t.");
-    element_types_ = static_cast<uint16_t>(rule_.element_types());
-
-    // Note: Normally we can not distinguish between the main plugin resource
-    // and any other loads it makes. We treat them both as OBJECT requests.
-    if (element_types_ & proto::ELEMENT_TYPE_OBJECT_SUBREQUEST)
-      element_types_ |= proto::ELEMENT_TYPE_OBJECT;
-
-    // Ignore unknown element types.
-    element_types_ &= proto::ELEMENT_TYPE_ALL;
-    // Filtering popups is not supported.
-    element_types_ &= ~proto::ELEMENT_TYPE_POPUP;
-
-    return true;
-  }
-
-  bool InitializeActivationTypes() {
-    static_assert(
-        proto::ACTIVATION_TYPE_ALL <= std::numeric_limits<uint8_t>::max(),
-        "Activation types can not be stored in uint8_t.");
-    activation_types_ = static_cast<uint8_t>(rule_.activation_types());
-
-    // Ignore unknown activation types.
-    activation_types_ &= proto::ACTIVATION_TYPE_ALL;
-    // No need in CSS activation, because the CSS rules are not supported.
-    activation_types_ &=
-        ~(proto::ACTIVATION_TYPE_ELEMHIDE | proto::ACTIVATION_TYPE_GENERICHIDE);
-
-    return true;
-  }
-
-  bool InitializeUrlPattern() {
-    switch (rule_.url_pattern_type()) {
-      case proto::URL_PATTERN_TYPE_SUBSTRING:
-        url_pattern_type_ = flat::UrlPatternType_SUBSTRING;
-        break;
-      case proto::URL_PATTERN_TYPE_WILDCARDED:
-        url_pattern_type_ = flat::UrlPatternType_WILDCARDED;
-        break;
-
-      // TODO(pkalinnikov): Implement REGEXP rules matching.
-      case proto::URL_PATTERN_TYPE_REGEXP:
-      default:
-        return false;  // Unsupported URL pattern type.
-    }
-
-    if (!ConvertAnchorType(rule_.anchor_left(), &anchor_left_) ||
-        !ConvertAnchorType(rule_.anchor_right(), &anchor_right_)) {
-      return false;
-    }
-    if (anchor_right_ == flat::AnchorType_SUBDOMAIN)
-      return false;  // Unsupported right anchor.
-
-    return true;
-  }
-
-  // Returns whether the rule is not a no-op after all the modifications above.
-  bool IsMeaningful() const { return element_types_ || activation_types_; }
-
-  const proto::UrlRule& rule_;
-
-  uint8_t options_ = 0;
-  uint16_t element_types_ = 0;
-  uint8_t activation_types_ = 0;
-  flat::UrlPatternType url_pattern_type_ = flat::UrlPatternType_WILDCARDED;
-  flat::AnchorType anchor_left_ = flat::AnchorType_NONE;
-  flat::AnchorType anchor_right_ = flat::AnchorType_NONE;
-
-  bool is_convertible_ = true;
-};
-
-}  // namespace
-
 // RulesetIndexer --------------------------------------------------------------
 
 // static
 const int RulesetIndexer::kIndexedFormatVersion = 17;
 
-RulesetIndexer::MutableUrlPatternIndex::MutableUrlPatternIndex() = default;
-RulesetIndexer::MutableUrlPatternIndex::~MutableUrlPatternIndex() = default;
+RulesetIndexer::RulesetIndexer()
+    : blacklist_(&builder_), whitelist_(&builder_), deactivation_(&builder_) {}
 
-RulesetIndexer::RulesetIndexer() = default;
 RulesetIndexer::~RulesetIndexer() = default;
 
 bool RulesetIndexer::AddUrlRule(const proto::UrlRule& rule) {
-  UrlRuleFlatBufferConverter converter(rule);
-  if (!converter.is_convertible())
+  const UrlRuleOffset offset = SerializeUrlRule(rule, &builder_);
+  // Note: A zero offset.o means a "nullptr" offset. It is returned when the
+  // rule has not been serialized.
+  if (!offset.o)
     return false;
-  DCHECK_NE(rule.url_pattern_type(), proto::URL_PATTERN_TYPE_REGEXP);
-  auto rule_offset = converter.SerializeConvertedRule(&builder_);
-
-  auto add_rule_to_index = [&rule, rule_offset](MutableUrlPatternIndex* index) {
-    NGram ngram =
-        GetMostDistinctiveNGram(index->ngram_index, rule.url_pattern());
-    if (ngram) {
-      index->ngram_index[ngram].push_back(rule_offset);
-    } else {
-      // TODO(pkalinnikov): Index fallback rules as well.
-      index->fallback_rules.push_back(rule_offset);
-    }
-  };
 
   if (rule.semantics() == proto::RULE_SEMANTICS_BLACKLIST) {
-    add_rule_to_index(&blacklist_);
+    blacklist_.IndexUrlRule(offset);
   } else {
-    if (converter.has_element_types())
-      add_rule_to_index(&whitelist_);
-    if (converter.has_activation_types())
-      add_rule_to_index(&activation_);
+    const auto* flat_rule = flatbuffers::GetTemporaryPointer(builder_, offset);
+    DCHECK(flat_rule);
+    if (flat_rule->element_types())
+      whitelist_.IndexUrlRule(offset);
+    if (flat_rule->activation_types())
+      deactivation_.IndexUrlRule(offset);
   }
 
   return true;
 }
 
 void RulesetIndexer::Finish() {
-  auto blacklist_offset = SerializeUrlPatternIndex(blacklist_);
-  auto whitelist_offset = SerializeUrlPatternIndex(whitelist_);
-  auto activation_offset = SerializeUrlPatternIndex(activation_);
+  auto blacklist_offset = blacklist_.Finish();
+  auto whitelist_offset = whitelist_.Finish();
+  auto deactivation_offset = deactivation_.Finish();
 
   auto url_rules_index_offset = flat::CreateIndexedRuleset(
-      builder_, blacklist_offset, whitelist_offset, activation_offset);
+      builder_, blacklist_offset, whitelist_offset, deactivation_offset);
   builder_.Finish(url_rules_index_offset);
 }
 
-// static
-NGram RulesetIndexer::GetMostDistinctiveNGram(
-    const MutableNGramIndex& ngram_index,
-    base::StringPiece pattern) {
-  size_t min_list_size = std::numeric_limits<size_t>::max();
-  NGram best_ngram = 0;
-
-  auto ngrams = CreateNGramExtractor<kNGramSize, NGram>(
-      pattern, [](char c) { return c == '*' || c == '^'; });
-
-  for (uint64_t ngram : ngrams) {
-    const MutableUrlRuleList* rules = ngram_index.Get(ngram);
-    const size_t list_size = rules ? rules->size() : 0;
-    if (list_size < min_list_size) {
-      // TODO(pkalinnikov): Pick random of the same-sized lists.
-      min_list_size = list_size;
-      best_ngram = ngram;
-      if (list_size == 0)
-        break;
-    }
-  }
-
-  return best_ngram;
-}
-
-flatbuffers::Offset<flat::UrlPatternIndex>
-RulesetIndexer::SerializeUrlPatternIndex(const MutableUrlPatternIndex& index) {
-  const MutableNGramIndex& ngram_index = index.ngram_index;
-
-  std::vector<flatbuffers::Offset<flat::NGramToRules>> flat_hash_table(
-      ngram_index.table_size());
-
-  flatbuffers::Offset<flat::NGramToRules> empty_slot_offset =
-      flat::CreateNGramToRules(builder_);
-  for (size_t i = 0, size = ngram_index.table_size(); i != size; ++i) {
-    const uint32_t entry_index = ngram_index.hash_table()[i];
-    if (entry_index >= ngram_index.size()) {
-      flat_hash_table[i] = empty_slot_offset;
-      continue;
-    }
-    const MutableNGramIndex::EntryType& entry =
-        ngram_index.entries()[entry_index];
-    auto rules_offset = builder_.CreateVector(entry.second);
-    flat_hash_table[i] =
-        flat::CreateNGramToRules(builder_, entry.first, rules_offset);
-  }
-  auto ngram_index_offset = builder_.CreateVector(flat_hash_table);
-
-  auto fallback_rules_offset = builder_.CreateVector(index.fallback_rules);
-
-  return flat::CreateUrlPatternIndex(builder_, kNGramSize, ngram_index_offset,
-                                     empty_slot_offset, fallback_rules_offset);
-}
-
 // IndexedRulesetMatcher -------------------------------------------------------
 
-namespace {
-
-using FlatUrlRuleList = flatbuffers::Vector<flatbuffers::Offset<flat::UrlRule>>;
-using FlatNGramIndex =
-    flatbuffers::Vector<flatbuffers::Offset<flat::NGramToRules>>;
-
-// Returns the size of the longest (sub-)domain of |origin| matching one of the
-// |domains| in the list.
-//
-// The |domains| should be sorted in descending order of their length, and
-// ascending alphabetical order within the groups of same-length domains.
-size_t GetLongestMatchingSubdomain(const url::Origin& origin,
-                                   const FlatDomains& domains) {
-  // If the |domains| list is short, then the simple strategy is usually faster.
-  if (domains.size() <= 5) {
-    for (auto* domain : domains) {
-      const base::StringPiece domain_piece = ToStringPiece(domain);
-      if (origin.DomainIs(domain_piece))
-        return domain_piece.size();
-    }
-    return 0;
-  }
-  // Otherwise look for each subdomain of the |origin| using binary search.
-
-  DCHECK(!origin.unique());
-  base::StringPiece canonicalized_host(origin.host());
-  if (canonicalized_host.empty())
-    return 0;
-
-  // If the host name ends with a dot, then ignore it.
-  if (canonicalized_host.back() == '.')
-    canonicalized_host.remove_suffix(1);
-
-  // The |left| bound of the search is shared between iterations, because
-  // subdomains are considered in decreasing order of their lengths, therefore
-  // each consecutive lower_bound will be at least as far as the previous.
-  flatbuffers::uoffset_t left = 0;
-  for (size_t position = 0;; ++position) {
-    const base::StringPiece subdomain = canonicalized_host.substr(position);
-
-    flatbuffers::uoffset_t right = domains.size();
-    while (left + 1 < right) {
-      auto middle = left + (right - left) / 2;
-      DCHECK_LT(middle, domains.size());
-      if (CompareDomains(ToStringPiece(domains[middle]), subdomain) <= 0)
-        left = middle;
-      else
-        right = middle;
-    }
-
-    DCHECK_LT(left, domains.size());
-    if (ToStringPiece(domains[left]) == subdomain)
-      return subdomain.size();
-
-    position = canonicalized_host.find('.', position);
-    if (position == base::StringPiece::npos)
-      break;
-  }
-
-  return 0;
-}
-
-// Returns whether the |origin| matches the domain list of the |rule|. A match
-// means that the longest domain in |domains| that |origin| is a sub-domain of
-// is not an exception OR all the |domains| are exceptions and neither matches
-// the |origin|. Thus, domain filters with more domain components trump filters
-// with fewer domain components, i.e. the more specific a filter is, the higher
-// the priority.
-//
-// A rule whose domain list is empty or contains only negative domains is still
-// considered a "generic" rule. Therefore, if |disable_generic_rules| is set,
-// this function will always return false for such rules.
-bool DoesOriginMatchDomainList(const url::Origin& origin,
-                               const flat::UrlRule& rule,
-                               bool disable_generic_rules) {
-  const bool is_generic = !rule.domains_included();
-  DCHECK(is_generic || rule.domains_included()->size());
-  if (disable_generic_rules && is_generic)
-    return false;
-
-  // Unique |origin| matches lists of exception domains only.
-  if (origin.unique())
-    return is_generic;
-
-  size_t longest_matching_included_domain_length = 1;
-  if (!is_generic) {
-    longest_matching_included_domain_length =
-        GetLongestMatchingSubdomain(origin, *rule.domains_included());
-  }
-  if (longest_matching_included_domain_length && rule.domains_excluded()) {
-    return GetLongestMatchingSubdomain(origin, *rule.domains_excluded()) <
-           longest_matching_included_domain_length;
-  }
-  return !!longest_matching_included_domain_length;
-}
-
-// Returns whether the request matches flags of the specified URL |rule|. Takes
-// into account:
-//  - |element_type| of the requested resource, if not *_UNSPECIFIED.
-//  - |activation_type| for a subdocument request, if not *_UNSPECIFIED.
-//  - Whether the resource |is_third_party| w.r.t. its embedding document.
-bool DoesRuleFlagsMatch(const flat::UrlRule& rule,
-                        proto::ElementType element_type,
-                        proto::ActivationType activation_type,
-                        bool is_third_party) {
-  DCHECK(element_type == proto::ELEMENT_TYPE_UNSPECIFIED ||
-         activation_type == proto::ACTIVATION_TYPE_UNSPECIFIED);
-
-  if (element_type != proto::ELEMENT_TYPE_UNSPECIFIED &&
-      !(rule.element_types() & element_type)) {
-    return false;
-  }
-  if (activation_type != proto::ACTIVATION_TYPE_UNSPECIFIED &&
-      !(rule.activation_types() & activation_type)) {
-    return false;
-  }
-
-  if (is_third_party &&
-      !(rule.options() & flat::OptionFlag_APPLIES_TO_THIRD_PARTY)) {
-    return false;
-  }
-  if (!is_third_party &&
-      !(rule.options() & flat::OptionFlag_APPLIES_TO_FIRST_PARTY)) {
-    return false;
-  }
-
-  return true;
-}
-
-bool MatchesAny(const FlatUrlRuleList* rules,
-                const GURL& url,
-                const url::Origin& document_origin,
-                proto::ElementType element_type,
-                proto::ActivationType activation_type,
-                bool is_third_party,
-                bool disable_generic_rules) {
-  if (!rules)
-    return false;
-  for (const flat::UrlRule* rule : *rules) {
-    DCHECK_NE(rule, nullptr);
-    DCHECK_NE(rule->url_pattern_type(), flat::UrlPatternType_REGEXP);
-    if (!DoesRuleFlagsMatch(*rule, element_type, activation_type,
-                            is_third_party)) {
-      continue;
-    }
-    if (!UrlPattern(*rule).MatchesUrl(url))
-      continue;
-
-    if (DoesOriginMatchDomainList(document_origin, *rule,
-                                  disable_generic_rules)) {
-      return true;
-    }
-  }
-
-  return false;
-}
-
-// Returns whether the network request matches a particular part of the index.
-// |is_third_party| should reflect the relation between |url| and
-// |document_origin|.
-bool IsMatch(const flat::UrlPatternIndex* index,
-             const GURL& url,
-             const url::Origin& document_origin,
-             proto::ElementType element_type,
-             proto::ActivationType activation_type,
-             bool is_third_party,
-             bool disable_generic_rules) {
-  if (!index)
-    return false;
-  const FlatNGramIndex* hash_table = index->ngram_index();
-  const flat::NGramToRules* empty_slot = index->ngram_index_empty_slot();
-  DCHECK_NE(hash_table, nullptr);
-
-  NGramHashTableProber prober;
-
-  auto ngrams = CreateNGramExtractor<kNGramSize, uint64_t>(
-      url.spec(), [](char) { return false; });
-  for (uint64_t ngram : ngrams) {
-    const size_t slot_index = prober.FindSlot(
-        ngram, base::strict_cast<size_t>(hash_table->size()),
-        [hash_table, empty_slot](NGram ngram, size_t slot_index) {
-          const flat::NGramToRules* entry = hash_table->Get(slot_index);
-          DCHECK_NE(entry, nullptr);
-          return entry == empty_slot || entry->ngram() == ngram;
-        });
-    DCHECK_LT(slot_index, hash_table->size());
-
-    const flat::NGramToRules* entry = hash_table->Get(slot_index);
-    if (entry == empty_slot)
-      continue;
-    if (MatchesAny(entry->rule_list(), url, document_origin, element_type,
-                   activation_type, is_third_party, disable_generic_rules)) {
-      return true;
-    }
-  }
-
-  const FlatUrlRuleList* rules = index->fallback_rules();
-  return MatchesAny(rules, url, document_origin, element_type, activation_type,
-                    is_third_party, disable_generic_rules);
-}
-
-}  // namespace
-
 // static
 bool IndexedRulesetMatcher::Verify(const uint8_t* buffer, size_t size) {
   flatbuffers::Verifier verifier(buffer, size);
@@ -563,24 +61,18 @@
 }
 
 IndexedRulesetMatcher::IndexedRulesetMatcher(const uint8_t* buffer, size_t size)
-    : root_(flat::GetIndexedRuleset(buffer)) {
-  const flat::UrlPatternIndex* index = root_->blacklist_index();
-  DCHECK(!index || index->n() == kNGramSize);
-  index = root_->whitelist_index();
-  DCHECK(!index || index->n() == kNGramSize);
-}
+    : root_(flat::GetIndexedRuleset(buffer)),
+      blacklist_(root_->blacklist_index()),
+      whitelist_(root_->whitelist_index()),
+      deactivation_(root_->deactivation_index()) {}
 
 bool IndexedRulesetMatcher::ShouldDisableFilteringForDocument(
     const GURL& document_url,
     const url::Origin& parent_document_origin,
     proto::ActivationType activation_type) const {
-  if (!document_url.is_valid() ||
-      activation_type == proto::ACTIVATION_TYPE_UNSPECIFIED) {
-    return false;
-  }
-  return IsMatch(
-      root_->activation_index(), document_url, parent_document_origin,
-      proto::ELEMENT_TYPE_UNSPECIFIED, activation_type,
+  return !!deactivation_.FindMatch(
+      document_url, parent_document_origin, proto::ELEMENT_TYPE_UNSPECIFIED,
+      activation_type,
       FirstPartyOrigin::IsThirdParty(document_url, parent_document_origin),
       false);
 }
@@ -590,15 +82,13 @@
     const FirstPartyOrigin& first_party,
     proto::ElementType element_type,
     bool disable_generic_rules) const {
-  if (!url.is_valid() || element_type == proto::ELEMENT_TYPE_UNSPECIFIED)
-    return false;
   const bool is_third_party = first_party.IsThirdParty(url);
-  return IsMatch(root_->blacklist_index(), url, first_party.origin(),
-                 element_type, proto::ACTIVATION_TYPE_UNSPECIFIED,
-                 is_third_party, disable_generic_rules) &&
-         !IsMatch(root_->whitelist_index(), url, first_party.origin(),
-                  element_type, proto::ACTIVATION_TYPE_UNSPECIFIED,
-                  is_third_party, disable_generic_rules);
+  return !!blacklist_.FindMatch(url, first_party.origin(), element_type,
+                                proto::ACTIVATION_TYPE_UNSPECIFIED,
+                                is_third_party, disable_generic_rules) &&
+         !whitelist_.FindMatch(url, first_party.origin(), element_type,
+                               proto::ACTIVATION_TYPE_UNSPECIFIED,
+                               is_third_party, disable_generic_rules);
 }
 
 }  // namespace subresource_filter
diff --git a/components/subresource_filter/core/common/indexed_ruleset.h b/components/subresource_filter/core/common/indexed_ruleset.h
index e5206d7..00588f8a 100644
--- a/components/subresource_filter/core/common/indexed_ruleset.h
+++ b/components/subresource_filter/core/common/indexed_ruleset.h
@@ -5,33 +5,28 @@
 #ifndef COMPONENTS_SUBRESOURCE_FILTER_CORE_COMMON_INDEXED_RULESET_H_
 #define COMPONENTS_SUBRESOURCE_FILTER_CORE_COMMON_INDEXED_RULESET_H_
 
+#include <stddef.h>
 #include <stdint.h>
 
-#include <vector>
-
 #include "base/macros.h"
 #include "base/numerics/safe_conversions.h"
-#include "base/strings/string_piece.h"
-#include "components/subresource_filter/core/common/closed_hash_map.h"
-#include "components/subresource_filter/core/common/flat/rules_generated.h"
-#include "components/subresource_filter/core/common/proto/rules.pb.h"
-#include "components/subresource_filter/core/common/uint64_hasher.h"
-#include "url/gurl.h"
-#include "url/origin.h"
+#include "components/subresource_filter/core/common/flat/indexed_ruleset_generated.h"
+#include "components/subresource_filter/core/common/url_pattern_index.h"
+#include "third_party/flatbuffers/src/include/flatbuffers/flatbuffers.h"
+
+class GURL;
+
+namespace url {
+class Origin;
+}
 
 namespace subresource_filter {
 
 class FirstPartyOrigin;
 
-// The integer type used to represent N-grams.
-using NGram = uint64_t;
-// The hasher used for hashing N-grams.
-using NGramHasher = Uint64Hasher;
-// The hash table probe sequence used both by the ruleset builder and matcher.
-using NGramHashTableProber = DefaultProber<NGram, NGramHasher>;
-
-constexpr size_t kNGramSize = 5;
-static_assert(kNGramSize <= sizeof(NGram), "NGram type is too narrow.");
+namespace proto {
+class UrlRule;
+}
 
 // The class used to construct flat data structures representing the set of URL
 // filtering rules, as well as the index of those. Internally owns a
@@ -66,45 +61,12 @@
   size_t size() const { return base::strict_cast<size_t>(builder_.GetSize()); }
 
  private:
-  using MutableUrlRuleList = std::vector<flatbuffers::Offset<flat::UrlRule>>;
-  using MutableNGramIndex =
-      ClosedHashMap<NGram, MutableUrlRuleList, NGramHashTableProber>;
-
-  // Encapsulates a subset of the rules, and an index built on the URL patterns
-  // in these rules. The ruleset is divided into parts according to metadata of
-  // the rules. Currently there are two parts: blacklist and whitelist.
-  struct MutableUrlPatternIndex {
-    // This index contains all non-REGEXP rules that have at least one
-    // acceptable N-gram. For a single rule the N-gram used as an index key is
-    // picked greedily (see GetMostDistinctiveNGram).
-    MutableNGramIndex ngram_index;
-
-    // A fallback list that contains all the rules with no acceptable N-gram,
-    // and all the REGEXP rules.
-    MutableUrlRuleList fallback_rules;
-
-    MutableUrlPatternIndex();
-    ~MutableUrlPatternIndex();
-  };
-
-  // Returns an N-gram of the |pattern| encoded into the NGram integer type. The
-  // N-gram is picked using a greedy heuristic, i.e. the one is chosen which
-  // corresponds to the shortest list of rules within the |index|. If there are
-  // no valid N-grams in the |pattern|, the return value is 0.
-  static NGram GetMostDistinctiveNGram(const MutableNGramIndex& index,
-                                       base::StringPiece pattern);
-
-  // Serialized an |index| built over a part of the ruleset, and returns its
-  // offset in the FlatBuffer.
-  flatbuffers::Offset<flat::UrlPatternIndex> SerializeUrlPatternIndex(
-      const MutableUrlPatternIndex& index);
-
-  MutableUrlPatternIndex blacklist_;
-  MutableUrlPatternIndex whitelist_;
-  MutableUrlPatternIndex activation_;
-
   flatbuffers::FlatBufferBuilder builder_;
 
+  UrlPatternIndexBuilder blacklist_;
+  UrlPatternIndexBuilder whitelist_;
+  UrlPatternIndexBuilder deactivation_;
+
   DISALLOW_COPY_AND_ASSIGN(RulesetIndexer);
 };
 
@@ -142,6 +104,10 @@
  private:
   const flat::IndexedRuleset* root_;
 
+  UrlPatternIndexMatcher blacklist_;
+  UrlPatternIndexMatcher whitelist_;
+  UrlPatternIndexMatcher deactivation_;
+
   DISALLOW_COPY_AND_ASSIGN(IndexedRulesetMatcher);
 };
 
diff --git a/components/subresource_filter/core/common/indexed_ruleset_unittest.cc b/components/subresource_filter/core/common/indexed_ruleset_unittest.cc
index 998fd74..41778883 100644
--- a/components/subresource_filter/core/common/indexed_ruleset_unittest.cc
+++ b/components/subresource_filter/core/common/indexed_ruleset_unittest.cc
@@ -10,158 +10,78 @@
 
 #include "base/logging.h"
 #include "base/macros.h"
-#include "base/strings/string_util.h"
+#include "base/strings/string_piece.h"
 #include "components/subresource_filter/core/common/first_party_origin.h"
 #include "components/subresource_filter/core/common/proto/rules.pb.h"
 #include "components/subresource_filter/core/common/url_pattern.h"
+#include "components/subresource_filter/core/common/url_rule_test_support.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+#include "url/origin.h"
 
 namespace subresource_filter {
 
-namespace {
+using namespace testing;
 
-constexpr proto::AnchorType kAnchorNone = proto::ANCHOR_TYPE_NONE;
-constexpr proto::AnchorType kBoundary = proto::ANCHOR_TYPE_BOUNDARY;
-constexpr proto::AnchorType kSubdomain = proto::ANCHOR_TYPE_SUBDOMAIN;
-constexpr proto::UrlPatternType kSubstring = proto::URL_PATTERN_TYPE_SUBSTRING;
-
-constexpr proto::SourceType kAnyParty = proto::SOURCE_TYPE_ANY;
-constexpr proto::SourceType kFirstParty = proto::SOURCE_TYPE_FIRST_PARTY;
-constexpr proto::SourceType kThirdParty = proto::SOURCE_TYPE_THIRD_PARTY;
-
-constexpr proto::ElementType kAllElementTypes = proto::ELEMENT_TYPE_ALL;
-constexpr proto::ElementType kOther = proto::ELEMENT_TYPE_OTHER;
-constexpr proto::ElementType kImage = proto::ELEMENT_TYPE_IMAGE;
-constexpr proto::ElementType kFont = proto::ELEMENT_TYPE_FONT;
-constexpr proto::ElementType kScript = proto::ELEMENT_TYPE_SCRIPT;
-constexpr proto::ElementType kPopup = proto::ELEMENT_TYPE_POPUP;
-constexpr proto::ElementType kWebSocket = proto::ELEMENT_TYPE_WEBSOCKET;
-
-constexpr proto::ActivationType kDocument = proto::ACTIVATION_TYPE_DOCUMENT;
-constexpr proto::ActivationType kGenericBlock =
-    proto::ACTIVATION_TYPE_GENERICBLOCK;
-
-// Note: Returns unique origin on origin_string == nullptr.
-url::Origin GetOrigin(const char* origin_string) {
-  return origin_string ? url::Origin(GURL(origin_string)) : url::Origin();
-}
-
-class UrlRuleBuilder {
+class SubresourceFilterIndexedRulesetTest : public ::testing::Test {
  public:
-  explicit UrlRuleBuilder(const UrlPattern& url_pattern,
-                          bool is_whitelist = false)
-      : UrlRuleBuilder(url_pattern, kAnyParty, is_whitelist) {}
-
-  UrlRuleBuilder(const UrlPattern& url_pattern,
-                 proto::SourceType source_type,
-                 bool is_whitelist) {
-    rule_.set_semantics(is_whitelist ? proto::RULE_SEMANTICS_WHITELIST
-                                     : proto::RULE_SEMANTICS_BLACKLIST);
-
-    rule_.set_source_type(source_type);
-    rule_.set_element_types(kAllElementTypes);
-
-    rule_.set_url_pattern_type(url_pattern.type());
-    rule_.set_anchor_left(url_pattern.anchor_left());
-    rule_.set_anchor_right(url_pattern.anchor_right());
-    rule_.set_match_case(url_pattern.match_case());
-    rule_.set_url_pattern(url_pattern.url_pattern().as_string());
-  }
-
-  UrlRuleBuilder& AddDomain(std::string domain_pattern) {
-    DCHECK(!domain_pattern.empty());
-    auto* domain = rule_.add_domains();
-    if (domain_pattern[0] == '~') {
-      domain_pattern.erase(0, 1);
-      domain->set_exclude(true);
-    }
-    domain->set_domain(domain_pattern);
-    return *this;
-  }
-
-  UrlRuleBuilder& AddDomains(const std::vector<std::string>& domains) {
-    for (const std::string domain : domains)
-      AddDomain(domain);
-    return *this;
-  }
-
-  const proto::UrlRule& rule() const { return rule_; }
-  proto::UrlRule& rule() { return rule_; }
-
- private:
-  proto::UrlRule rule_;
-
-  DISALLOW_COPY_AND_ASSIGN(UrlRuleBuilder);
-};
-
-}  // namespace
-
-class SubresourceFilterIndexedRulesetTest : public testing::Test {
- public:
-  SubresourceFilterIndexedRulesetTest() = default;
+  SubresourceFilterIndexedRulesetTest() { Reset(); }
 
  protected:
-  bool ShouldAllow(const char* url,
-                   const char* document_origin = nullptr,
+  bool ShouldAllow(base::StringPiece url,
+                   base::StringPiece document_origin = nullptr,
                    proto::ElementType element_type = kOther,
                    bool disable_generic_rules = false) const {
-    DCHECK_NE(matcher_.get(), nullptr);
-    url::Origin origin = GetOrigin(document_origin);
-    FirstPartyOrigin first_party(origin);
-    return !matcher_->ShouldDisallowResourceLoad(
-        GURL(url), first_party, element_type, disable_generic_rules);
-  }
-
-  bool ShouldAllow(const char* url,
-                   const char* document_origin,
-                   bool disable_generic_rules) const {
-    return ShouldAllow(url, document_origin, kOther, disable_generic_rules);
-  }
-
-  bool ShouldDeactivate(const char* document_url,
-                        const char* parent_document_origin = nullptr,
-                        proto::ActivationType activation_type =
-                            proto::ACTIVATION_TYPE_UNSPECIFIED) const {
     DCHECK(matcher_);
-    url::Origin origin = GetOrigin(parent_document_origin);
-    return matcher_->ShouldDisableFilteringForDocument(GURL(document_url),
-                                                       origin, activation_type);
+    return !matcher_->ShouldDisallowResourceLoad(
+        GURL(url), FirstPartyOrigin(GetOrigin(document_origin)), element_type,
+        disable_generic_rules);
   }
 
-  void AddUrlRule(const proto::UrlRule& rule) {
-    ASSERT_TRUE(indexer_.AddUrlRule(rule)) << "URL pattern: "
-                                           << rule.url_pattern();
+  bool ShouldDeactivate(
+      base::StringPiece document_url,
+      base::StringPiece parent_document_origin = nullptr,
+      proto::ActivationType activation_type = kNoActivation) const {
+    DCHECK(matcher_);
+    return matcher_->ShouldDisableFilteringForDocument(
+        GURL(document_url), GetOrigin(parent_document_origin), activation_type);
   }
 
-  void AddSimpleRule(const UrlPattern& url_pattern, bool is_whitelist) {
-    AddUrlRule(UrlRuleBuilder(url_pattern, is_whitelist).rule());
+  bool AddUrlRule(const proto::UrlRule& rule) {
+    return indexer_->AddUrlRule(rule);
   }
 
-  void AddBlacklistRule(const UrlPattern& url_pattern,
-                        proto::SourceType source_type = kAnyParty) {
-    AddUrlRule(UrlRuleBuilder(url_pattern, source_type, false).rule());
+  bool AddSimpleRule(base::StringPiece url_pattern) {
+    return AddUrlRule(MakeUrlRule(UrlPattern(url_pattern, kSubstring)));
   }
 
-  void AddWhitelistRuleWithActivationTypes(const UrlPattern& url_pattern,
-                                           int32_t activation_types) {
-    UrlRuleBuilder builder(url_pattern, kAnyParty, true);
-    builder.rule().set_element_types(proto::ELEMENT_TYPE_UNSPECIFIED);
-    builder.rule().set_activation_types(activation_types);
-    AddUrlRule(builder.rule());
+  bool AddSimpleWhitelistRule(base::StringPiece url_pattern) {
+    auto rule = MakeUrlRule(UrlPattern(url_pattern, kSubstring));
+    rule.set_semantics(proto::RULE_SEMANTICS_WHITELIST);
+    return AddUrlRule(rule);
+  }
+
+  bool AddSimpleWhitelistRule(base::StringPiece url_pattern,
+                              int32_t activation_types) {
+    auto rule = MakeUrlRule(url_pattern);
+    rule.set_semantics(proto::RULE_SEMANTICS_WHITELIST);
+    rule.clear_element_types();
+    rule.set_activation_types(activation_types);
+    return AddUrlRule(rule);
   }
 
   void Finish() {
-    indexer_.Finish();
-    matcher_.reset(new IndexedRulesetMatcher(indexer_.data(), indexer_.size()));
+    indexer_->Finish();
+    matcher_.reset(
+        new IndexedRulesetMatcher(indexer_->data(), indexer_->size()));
   }
 
   void Reset() {
     matcher_.reset(nullptr);
-    indexer_.~RulesetIndexer();
-    new (&indexer_) RulesetIndexer();
+    indexer_.reset(new RulesetIndexer);
   }
 
-  RulesetIndexer indexer_;
+  std::unique_ptr<RulesetIndexer> indexer_;
   std::unique_ptr<IndexedRulesetMatcher> matcher_;
 
  private:
@@ -175,138 +95,103 @@
     bool expect_allowed;
   } kTestCases[] = {
       // SUBSTRING
-      {{"abcd", kSubstring}, "http://example.com/abcd", false},
-      {{"abcd", kSubstring}, "http://example.com/dcab", true},
-      {{"42", kSubstring}, "http://example.com/adcd/picture42.png", false},
+      {{"abcd", kSubstring}, "http://ex.com/abcd", false},
+      {{"abcd", kSubstring}, "http://ex.com/dcab", true},
+      {{"42", kSubstring}, "http://ex.com/adcd/picture42.png", false},
       {{"&test", kSubstring},
-       "http://example.com/params?para1=false&test=true",
+       "http://ex.com/params?para1=false&test=true",
        false},
-      {{"-test-42.", kSubstring}, "http://example.com/unit-test-42.1", false},
+      {{"-test-42.", kSubstring}, "http://ex.com/unit-test-42.1", false},
       {{"/abcdtest160x600.", kSubstring},
-       "http://example.com/abcdtest160x600.png",
+       "http://ex.com/abcdtest160x600.png",
        false},
 
       // WILDCARDED
-      {{"http://example.com/abcd/picture*.png"},
-       "http://example.com/abcd/picture42.png",
+      {{"http://ex.com/abcd/picture*.png"},
+       "http://ex.com/abcd/picture42.png",
        false},
-      {{"example.com", kSubdomain, kAnchorNone}, "http://example.com", false},
-      {{"example.com", kSubdomain, kAnchorNone},
-       "http://test.example.com",
-       false},
-      {{"example.com", kSubdomain, kAnchorNone},
-       "https://test.example.com.com",
-       false},
-      {{"example.com", kSubdomain, kAnchorNone},
-       "https://test.rest.example.com",
-       false},
-      {{"example.com", kSubdomain, kAnchorNone},
-       "https://test_example.com",
-       true},
+      {{"ex.com", kSubdomain, kAnchorNone}, "http://ex.com", false},
+      {{"ex.com", kSubdomain, kAnchorNone}, "http://test.ex.com", false},
+      {{"ex.com", kSubdomain, kAnchorNone}, "https://test.ex.com.com", false},
+      {{"ex.com", kSubdomain, kAnchorNone}, "https://test.rest.ex.com", false},
+      {{"ex.com", kSubdomain, kAnchorNone}, "https://test_ex.com", true},
 
-      {{"http://example.com", kBoundary, kAnchorNone},
-       "http://example.com/",
+      {{"http://ex.com", kBoundary, kAnchorNone}, "http://ex.com/", false},
+      {{"http://ex.com", kBoundary, kAnchorNone}, "http://ex.com/42", false},
+      {{"http://ex.com", kBoundary, kAnchorNone},
+       "http://ex.com/42/http://ex.com/",
        false},
-      {{"http://example.com", kBoundary, kAnchorNone},
-       "http://example.com/42",
+      {{"http://ex.com", kBoundary, kAnchorNone},
+       "http://ex.com/42/http://ex.info/",
        false},
-      {{"http://example.com", kBoundary, kAnchorNone},
-       "http://example.com/42/http://example.com/",
-       false},
-      {{"http://example.com", kBoundary, kAnchorNone},
-       "http://example.com/42/http://example.info/",
-       false},
-      {{"http://example.com/", kBoundary, kBoundary},
-       "http://example.com",
-       false},
-      {{"http://example.com/", kBoundary, kBoundary},
-       "http://example.com/42",
+      {{"http://ex.com/", kBoundary, kBoundary}, "http://ex.com", false},
+      {{"http://ex.com/", kBoundary, kBoundary}, "http://ex.com/42", true},
+      {{"http://ex.com/", kBoundary, kBoundary},
+       "http://ex.info/42/http://ex.com/",
        true},
-      {{"http://example.com/", kBoundary, kBoundary},
-       "http://example.info/42/http://example.com/",
+      {{"http://ex.com/", kBoundary, kBoundary},
+       "http://ex.info/42/http://ex.com/",
        true},
-      {{"http://example.com/", kBoundary, kBoundary},
-       "http://example.info/42/http://example.com/",
+      {{"http://ex.com/", kBoundary, kBoundary}, "http://ex.com/", false},
+      {{"http://ex.com/", kBoundary, kBoundary}, "http://ex.com/42.swf", true},
+      {{"http://ex.com/", kBoundary, kBoundary},
+       "http://ex.info/redirect/http://ex.com/",
        true},
-      {{"http://example.com/", kBoundary, kBoundary},
-       "http://example.com/",
-       false},
-      {{"http://example.com/", kBoundary, kBoundary},
-       "http://example.com/42.swf",
-       true},
-      {{"http://example.com/", kBoundary, kBoundary},
-       "http://example.info/redirect/http://example.com/",
-       true},
-      {{"pdf", kAnchorNone, kBoundary}, "http://example.com/abcd.pdf", false},
-      {{"pdf", kAnchorNone, kBoundary}, "http://example.com/pdfium", true},
-      {{"http://example.com^"}, "http://example.com/", false},
-      {{"http://example.com^"}, "http://example.com:8000/", false},
-      {{"http://example.com^"}, "http://example.com.ru", true},
-      {{"^example.com^"},
-       "http://example.com:8000/42.loss?a=12&b=%D1%82%D0%B5%D1%81%D1%82",
+      {{"pdf", kAnchorNone, kBoundary}, "http://ex.com/abcd.pdf", false},
+      {{"pdf", kAnchorNone, kBoundary}, "http://ex.com/pdfium", true},
+      {{"http://ex.com^"}, "http://ex.com/", false},
+      {{"http://ex.com^"}, "http://ex.com:8000/", false},
+      {{"http://ex.com^"}, "http://ex.com.ru", true},
+      {{"^ex.com^"},
+       "http://ex.com:8000/42.loss?a=12&b=%D1%82%D0%B5%D1%81%D1%82",
        false},
       {{"^42.loss^"},
-       "http://example.com:8000/42.loss?a=12&b=%D1%82%D0%B5%D1%81%D1%82",
+       "http://ex.com:8000/42.loss?a=12&b=%D1%82%D0%B5%D1%81%D1%82",
        false},
 
-      // FIXME(pkalinnikov): The '^' at the end should match end-of-string.
+      // TODO(pkalinnikov): The '^' at the end should match end-of-string.
+      //
       // {"^%D1%82%D0%B5%D1%81%D1%82^",
-      //  "http://example.com:8000/42.loss?a=12&b=%D1%82%D0%B5%D1%81%D1%82",
+      //  "http://ex.com:8000/42.loss?a=12&b=%D1%82%D0%B5%D1%81%D1%82",
       //  false},
-      // {"/abcd/*/picture^", "http://example.com/abcd/42/picture", false},
+      // {"/abcd/*/picture^", "http://ex.com/abcd/42/picture", false},
 
-      {{"/abcd/*/picture^"},
-       "http://example.com/abcd/42/loss/picture?param",
+      {{"/abcd/*/picture^"}, "http://ex.com/abcd/42/loss/picture?param", false},
+      {{"/abcd/*/picture^"}, "http://ex.com/abcd//picture/42", false},
+      {{"/abcd/*/picture^"}, "http://ex.com/abcd/picture", true},
+      {{"/abcd/*/picture^"}, "http://ex.com/abcd/42/pictureraph", true},
+      {{"/abcd/*/picture^"}, "http://ex.com/abcd/42/picture.swf", true},
+      {{"test.ex.com^", kSubdomain, kAnchorNone},
+       "http://test.ex.com/42.swf",
        false},
-      {{"/abcd/*/picture^"}, "http://example.com/abcd//picture/42", false},
-      {{"/abcd/*/picture^"}, "http://example.com/abcd/picture", true},
-      {{"/abcd/*/picture^"}, "http://example.com/abcd/42/pictureraph", true},
-      {{"/abcd/*/picture^"}, "http://example.com/abcd/42/picture.swf", true},
-      {{"test.example.com^", kSubdomain, kAnchorNone},
-       "http://test.example.com/42.swf",
+      {{"test.ex.com^", kSubdomain, kAnchorNone},
+       "http://server1.test.ex.com/42.swf",
        false},
-      {{"test.example.com^", kSubdomain, kAnchorNone},
-       "http://server1.test.example.com/42.swf",
+      {{"test.ex.com^", kSubdomain, kAnchorNone},
+       "https://test.ex.com:8000/",
        false},
-      {{"test.example.com^", kSubdomain, kAnchorNone},
-       "https://test.example.com:8000/",
-       false},
-      {{"test.example.com^", kSubdomain, kAnchorNone},
-       "http://test.example.com.ua/42.swf",
+      {{"test.ex.com^", kSubdomain, kAnchorNone},
+       "http://test.ex.com.ua/42.swf",
        true},
-      {{"test.example.com^", kSubdomain, kAnchorNone},
-       "http://example.com/redirect/http://test.example.com/",
+      {{"test.ex.com^", kSubdomain, kAnchorNone},
+       "http://ex.com/redirect/http://test.ex.com/",
        true},
 
-      {{"/abcd/*"}, "https://example.com/abcd/", false},
-      {{"/abcd/*"}, "http://example.com/abcd/picture.jpeg", false},
-      {{"/abcd/*"}, "https://example.com/abcd", true},
-      {{"/abcd/*"}, "http://abcd.example.com", true},
-      {{"*/abcd/"}, "https://example.com/abcd/", false},
-      {{"*/abcd/"}, "http://example.com/abcd/picture.jpeg", false},
-      {{"*/abcd/"}, "https://example.com/test-abcd/", true},
-      {{"*/abcd/"}, "http://abcd.example.com", true},
-
-      // FIXME(pkalinnikov): Implement REGEXP matching.
-      // REGEXP
-      // {"/test|rest\\d+/", "http://example.com/test42", false},
-      // {"/test|rest\\d+/", "http://example.com/test", false},
-      // {"/test|rest\\d+/", "http://example.com/rest42", false},
-      // {"/test|rest\\d+/", "http://example.com/rest", true},
-      // {"/example\\.com/.*\\/[a-zA-Z0-9]{3}/", "http://example.com/abcd/42y",
-      //  false},
-      // {"/example\\.com/.*\\/[a-zA-Z0-9]{3}/", "http://example.com/abcd/%42y",
-      //  true},
-      // {"||example.com^*/test.htm", "http://example.com/unit/test.html",
-      // false},
-      // {"||example.com^*/test.htm", "http://examole.com/test.htm", true},
+      {{"/abcd/*"}, "https://ex.com/abcd/", false},
+      {{"/abcd/*"}, "http://ex.com/abcd/picture.jpeg", false},
+      {{"/abcd/*"}, "https://ex.com/abcd", true},
+      {{"/abcd/*"}, "http://abcd.ex.com", true},
+      {{"*/abcd/"}, "https://ex.com/abcd/", false},
+      {{"*/abcd/"}, "http://ex.com/abcd/picture.jpeg", false},
+      {{"*/abcd/"}, "https://ex.com/test-abcd/", true},
+      {{"*/abcd/"}, "http://abcd.ex.com", true},
   };
 
   for (const auto& test_case : kTestCases) {
-    SCOPED_TRACE(testing::Message() << "Rule: " << test_case.url_pattern
-                                    << "; URL: " << test_case.url);
+    SCOPED_TRACE(::testing::Message() << "UrlPattern: " << test_case.url_pattern
+                                      << "; URL: " << test_case.url);
 
-    AddBlacklistRule(test_case.url_pattern);
+    ASSERT_TRUE(AddUrlRule(MakeUrlRule(test_case.url_pattern)));
     Finish();
 
     EXPECT_EQ(test_case.expect_allowed, ShouldAllow(test_case.url));
@@ -323,47 +208,39 @@
     const char* document_origin;
     bool expect_allowed;
   } kTestCases[] = {
-      {"example.com", kThirdParty, "http://example.com", "http://exmpl.org",
+      {"ex.com", kThirdParty, "http://ex.com", "http://exmpl.org", false},
+      {"ex.com", kThirdParty, "http://ex.com", "http://ex.com", true},
+      {"ex.com", kThirdParty, "http://ex.com/path?k=v", "http://exmpl.org",
        false},
-      {"example.com", kThirdParty, "http://example.com", "http://example.com",
+      {"ex.com", kThirdParty, "http://ex.com/path?k=v", "http://ex.com", true},
+      {"ex.com", kFirstParty, "http://ex.com/path?k=v", "http://ex.com", false},
+      {"ex.com", kFirstParty, "http://ex.com/path?k=v", "http://exmpl.com",
        true},
-      {"example.com", kThirdParty, "http://example.com/path?k=v",
-       "http://exmpl.org", false},
-      {"example.com", kThirdParty, "http://example.com/path?k=v",
-       "http://example.com", true},
-      {"example.com", kFirstParty, "http://example.com/path?k=v",
-       "http://example.com", false},
-      {"example.com", kFirstParty, "http://example.com/path?k=v",
-       "http://exmpl.com", true},
-      {"example.com", kAnyParty, "http://example.com/path?k=v",
-       "http://example.com", false},
-      {"example.com", kAnyParty, "http://example.com/path?k=v",
-       "http://exmpl.com", false},
-      {"example.com", kThirdParty, "http://subdomain.example.com",
-       "http://example.com", true},
-      {"example.com", kThirdParty, "http://example.com", nullptr, false},
+      {"ex.com", kAnyParty, "http://ex.com/path?k=v", "http://ex.com", false},
+      {"ex.com", kAnyParty, "http://ex.com/path?k=v", "http://exmpl.com",
+       false},
+      {"ex.com", kThirdParty, "http://subdomain.ex.com", "http://ex.com", true},
+      {"ex.com", kThirdParty, "http://ex.com", nullptr, false},
 
       // Public Suffix List tests.
-      {"example.com", kThirdParty, "http://two.example.com",
-       "http://one.example.com", true},
-      {"example.com", kThirdParty, "http://example.com",
-       "http://one.example.com", true},
-      {"example.com", kThirdParty, "http://two.example.com",
-       "http://example.com", true},
-      {"example.com", kThirdParty, "http://example.com", "http://example.org",
-       false},
+      {"ex.com", kThirdParty, "http://two.ex.com", "http://one.ex.com", true},
+      {"ex.com", kThirdParty, "http://ex.com", "http://one.ex.com", true},
+      {"ex.com", kThirdParty, "http://two.ex.com", "http://ex.com", true},
+      {"ex.com", kThirdParty, "http://ex.com", "http://ex.org", false},
       {"appspot.com", kThirdParty, "http://two.appspot.org",
        "http://one.appspot.com", true},
   };
 
   for (auto test_case : kTestCases) {
-    SCOPED_TRACE(testing::Message()
-                 << "Rule: " << test_case.url_pattern << "; source: "
-                 << (int)test_case.source_type << "; URL: " << test_case.url
-                 << "; document: " << test_case.document_origin);
+    SCOPED_TRACE(::testing::Message()
+                 << "UrlPattern: " << test_case.url_pattern
+                 << "; SourceType: " << static_cast<int>(test_case.source_type)
+                 << "; URL: " << test_case.url
+                 << "; DocumentOrigin: " << test_case.document_origin);
 
-    AddBlacklistRule(UrlPattern(test_case.url_pattern, kSubstring),
-                     test_case.source_type);
+    auto rule = MakeUrlRule(UrlPattern(test_case.url_pattern, kSubstring));
+    rule.set_source_type(test_case.source_type);
+    ASSERT_TRUE(AddUrlRule(rule));
     Finish();
 
     EXPECT_EQ(test_case.expect_allowed,
@@ -378,7 +255,6 @@
   const struct {
     std::vector<std::string> domains;
     const char* document_origin;
-
     bool expect_allowed;
   } kTestCases[] = {
       {std::vector<std::string>(), nullptr, false},
@@ -469,13 +345,13 @@
   };
 
   for (const auto& test_case : kTestCases) {
-    SCOPED_TRACE(testing::Message()
-                 << "Domains: " << base::JoinString(test_case.domains, "|")
-                 << "; document: " << test_case.document_origin);
+    SCOPED_TRACE(::testing::Message()
+                 << "Domains: " << ::testing::PrintToString(test_case.domains)
+                 << "; DocumentOrigin: " << test_case.document_origin);
 
-    UrlRuleBuilder builder(UrlPattern(kUrl, kSubstring));
-    builder.AddDomains(test_case.domains);
-    AddUrlRule(builder.rule());
+    auto rule = MakeUrlRule(UrlPattern(kUrl, kSubstring));
+    AddDomains(test_case.domains, &rule);
+    ASSERT_TRUE(AddUrlRule(rule));
     Finish();
 
     EXPECT_EQ(test_case.expect_allowed,
@@ -503,26 +379,26 @@
     domains.push_back("~sub.sub.c.sub." + domain);
   }
 
-  UrlRuleBuilder builder(UrlPattern(kUrl, kSubstring));
-  builder.AddDomains(domains);
-  AddUrlRule(builder.rule());
+  auto rule = MakeUrlRule(UrlPattern(kUrl, kSubstring));
+  AddDomains(domains, &rule);
+  ASSERT_TRUE(AddUrlRule(rule));
   Finish();
 
   for (size_t i = 0; i < kDomains; ++i) {
-    SCOPED_TRACE(testing::Message() << "Iteration: " << i);
+    SCOPED_TRACE(::testing::Message() << "Iteration: " << i);
     const std::string domain = "domain" + std::to_string(i) + ".com";
 
-    EXPECT_FALSE(ShouldAllow(kUrl, ("http://" + domain).c_str()));
-    EXPECT_TRUE(ShouldAllow(kUrl, ("http://sub." + domain).c_str()));
-    EXPECT_FALSE(ShouldAllow(kUrl, ("http://a.sub." + domain).c_str()));
-    EXPECT_FALSE(ShouldAllow(kUrl, ("http://b.sub." + domain).c_str()));
-    EXPECT_FALSE(ShouldAllow(kUrl, ("http://c.sub." + domain).c_str()));
-    EXPECT_TRUE(ShouldAllow(kUrl, ("http://aa.sub." + domain).c_str()));
-    EXPECT_TRUE(ShouldAllow(kUrl, ("http://ab.sub." + domain).c_str()));
-    EXPECT_TRUE(ShouldAllow(kUrl, ("http://ba.sub." + domain).c_str()));
-    EXPECT_TRUE(ShouldAllow(kUrl, ("http://bb.sub." + domain).c_str()));
-    EXPECT_FALSE(ShouldAllow(kUrl, ("http://sub.c.sub." + domain).c_str()));
-    EXPECT_TRUE(ShouldAllow(kUrl, ("http://sub.sub.c.sub." + domain).c_str()));
+    EXPECT_FALSE(ShouldAllow(kUrl, "http://" + domain));
+    EXPECT_TRUE(ShouldAllow(kUrl, "http://sub." + domain));
+    EXPECT_FALSE(ShouldAllow(kUrl, "http://a.sub." + domain));
+    EXPECT_FALSE(ShouldAllow(kUrl, "http://b.sub." + domain));
+    EXPECT_FALSE(ShouldAllow(kUrl, "http://c.sub." + domain));
+    EXPECT_TRUE(ShouldAllow(kUrl, "http://aa.sub." + domain));
+    EXPECT_TRUE(ShouldAllow(kUrl, "http://ab.sub." + domain));
+    EXPECT_TRUE(ShouldAllow(kUrl, "http://ba.sub." + domain));
+    EXPECT_TRUE(ShouldAllow(kUrl, "http://bb.sub." + domain));
+    EXPECT_FALSE(ShouldAllow(kUrl, "http://sub.c.sub." + domain));
+    EXPECT_TRUE(ShouldAllow(kUrl, "http://sub.sub.c.sub." + domain));
   }
 }
 
@@ -559,14 +435,16 @@
   };
 
   for (const auto& test_case : kTestCases) {
-    SCOPED_TRACE(testing::Message()
-                 << "Rule: " << test_case.url_pattern << "; ElementTypes: "
-                 << (int)test_case.element_types << "; URL: " << test_case.url
-                 << "; ElementType: " << (int)test_case.element_type);
+    SCOPED_TRACE(
+        ::testing::Message()
+        << "UrlPattern: " << test_case.url_pattern
+        << "; ElementTypes: " << static_cast<int>(test_case.element_types)
+        << "; URL: " << test_case.url
+        << "; ElementType: " << static_cast<int>(test_case.element_type));
 
-    UrlRuleBuilder builder(UrlPattern(test_case.url_pattern, kSubstring));
-    builder.rule().set_element_types(test_case.element_types);
-    AddUrlRule(builder.rule());
+    auto rule = MakeUrlRule(UrlPattern(test_case.url_pattern, kSubstring));
+    rule.set_element_types(test_case.element_types);
+    ASSERT_TRUE(AddUrlRule(rule));
     Finish();
 
     EXPECT_EQ(test_case.expect_allowed,
@@ -600,15 +478,15 @@
   };
 
   for (const auto& test_case : kTestCases) {
-    SCOPED_TRACE(testing::Message()
-                 << "Rule: " << test_case.url_pattern
-                 << "; ActivationTypes: " << (int)test_case.activation_types
-                 << "; DocURL: " << test_case.document_url
-                 << "; ActivationType: " << (int)test_case.activation_type);
+    SCOPED_TRACE(
+        ::testing::Message()
+        << "UrlPattern: " << test_case.url_pattern
+        << "; ActivationTypes: " << static_cast<int>(test_case.activation_types)
+        << "; DocumentURL: " << test_case.document_url
+        << "; ActivationType: " << static_cast<int>(test_case.activation_type));
 
-    AddWhitelistRuleWithActivationTypes(
-        UrlPattern(test_case.url_pattern, kSubstring),
-        test_case.activation_types);
+    ASSERT_TRUE(AddSimpleWhitelistRule(test_case.url_pattern,
+                                       test_case.activation_types));
     Finish();
 
     EXPECT_EQ(test_case.expect_disabled,
@@ -626,11 +504,12 @@
 }
 
 TEST_F(SubresourceFilterIndexedRulesetTest, RuleWithElementAndActivationTypes) {
-  UrlRuleBuilder builder(UrlPattern("allow.ex.com"), true /* is_whitelist */);
-  builder.rule().set_activation_types(kDocument);
+  auto rule = MakeUrlRule(UrlPattern("allow.ex.com", kSubstring));
+  rule.set_semantics(proto::RULE_SEMANTICS_WHITELIST);
+  rule.set_activation_types(kDocument);
 
-  AddUrlRule(builder.rule());
-  AddBlacklistRule(UrlPattern("ex.com"));
+  ASSERT_TRUE(AddUrlRule(rule));
+  ASSERT_TRUE(AddSimpleRule("ex.com"));
   Finish();
 
   EXPECT_FALSE(ShouldAllow("http://ex.com"));
@@ -643,37 +522,35 @@
 }
 
 TEST_F(SubresourceFilterIndexedRulesetTest, MatchWithDisableGenericRules) {
-  // Generic rules.
-  ASSERT_NO_FATAL_FAILURE(
-      AddUrlRule(UrlRuleBuilder(UrlPattern("some_text", kSubstring)).rule()));
-  ASSERT_NO_FATAL_FAILURE(
-      AddUrlRule(UrlRuleBuilder(UrlPattern("another_text", kSubstring))
-                     .AddDomain("~example.com")
-                     .rule()));
+  const struct {
+    const char* url_pattern;
+    std::vector<std::string> domains;
+  } kRules[] = {
+      // Generic rules.
+      {"some_text", std::vector<std::string>()},
+      {"another_text", {"~example.com"}},
+      {"final_text", {"~example1.com", "~example2.com"}},
+      // Domain specific rules.
+      {"some_text", {"example1.com"}},
+      {"more_text", {"example.com", "~exclude.example.com"}},
+      {"last_text", {"example1.com", "sub.example2.com"}},
+  };
 
-  // Domain specific rules.
-  ASSERT_NO_FATAL_FAILURE(
-      AddUrlRule(UrlRuleBuilder(UrlPattern("some_text", kSubstring))
-                     .AddDomain("example1.com")
-                     .rule()));
-  ASSERT_NO_FATAL_FAILURE(
-      AddUrlRule(UrlRuleBuilder(UrlPattern("more_text", kSubstring))
-                     .AddDomain("example.com")
-                     .AddDomain("~exclude.example.com")
-                     .rule()));
-  ASSERT_NO_FATAL_FAILURE(
-      AddUrlRule(UrlRuleBuilder(UrlPattern("last_text", kSubstring))
-                     .AddDomain("example1.com")
-                     .AddDomain("sub.example2.com")
-                     .rule()));
+  for (const auto& rule_data : kRules) {
+    auto rule = MakeUrlRule(UrlPattern(rule_data.url_pattern, kSubstring));
+    AddDomains(rule_data.domains, &rule);
+    ASSERT_TRUE(AddUrlRule(rule))
+        << "UrlPattern: " << rule_data.url_pattern
+        << "; Domains: " << ::testing::PrintToString(rule_data.domains);
+  }
+
   // Note: Some of the rules have common domains (e.g., example1.com), which are
   // ultimately shared by FlatBuffers' CreateSharedString. The test also makes
   // sure that the data structure works properly with such optimization.
-
   Finish();
 
   const struct {
-    const char* url_pattern;
+    const char* url;
     const char* document_origin;
     bool should_allow_with_disable_generic_rules;
     bool should_allow_with_enable_all_rules;
@@ -684,6 +561,10 @@
       {"http://ex.com/another_text", "http://example.com", true, true},
       {"http://ex.com/another_text", "http://example1.com", true, false},
 
+      {"http://ex.com/final_text", "http://example.com", true, false},
+      {"http://ex.com/final_text", "http://example1.com", true, true},
+      {"http://ex.com/final_text", "http://example2.com", true, true},
+
       {"http://ex.com/more_text", "http://example.com", false, false},
       {"http://ex.com/more_text", "http://exclude.example.com", true, true},
       {"http://ex.com/more_text", "http://example1.com", true, true},
@@ -697,37 +578,37 @@
   constexpr bool kDisableGenericRules = true;
   constexpr bool kEnableAllRules = false;
   for (const auto& test_case : kTestCases) {
-    SCOPED_TRACE(testing::Message()
-                 << "Url: " << test_case.url_pattern
-                 << "; document: " << test_case.document_origin);
+    SCOPED_TRACE(::testing::Message()
+                 << "URL: " << test_case.url
+                 << "; DocumentOrigin: " << test_case.document_origin);
 
     EXPECT_EQ(test_case.should_allow_with_disable_generic_rules,
-              ShouldAllow(test_case.url_pattern, test_case.document_origin,
+              ShouldAllow(test_case.url, test_case.document_origin, kOther,
                           kDisableGenericRules));
     EXPECT_EQ(test_case.should_allow_with_enable_all_rules,
-              ShouldAllow(test_case.url_pattern, test_case.document_origin,
+              ShouldAllow(test_case.url, test_case.document_origin, kOther,
                           kEnableAllRules));
   }
 }
 
 TEST_F(SubresourceFilterIndexedRulesetTest, EmptyRuleset) {
   Finish();
+  EXPECT_TRUE(ShouldAllow(nullptr));
   EXPECT_TRUE(ShouldAllow("http://example.com"));
   EXPECT_TRUE(ShouldAllow("http://another.example.com?param=val"));
-  EXPECT_TRUE(ShouldAllow(nullptr));
 }
 
 TEST_F(SubresourceFilterIndexedRulesetTest, NoRuleApplies) {
-  AddSimpleRule(UrlPattern("?filtered_content=", kSubstring), false);
-  AddSimpleRule(UrlPattern("&filtered_content=", kSubstring), false);
+  ASSERT_TRUE(AddSimpleRule("?filter_out="));
+  ASSERT_TRUE(AddSimpleRule("&filter_out="));
   Finish();
 
   EXPECT_TRUE(ShouldAllow("http://example.com"));
-  EXPECT_TRUE(ShouldAllow("http://example.com?filtered_not"));
+  EXPECT_TRUE(ShouldAllow("http://example.com?filter_not"));
 }
 
 TEST_F(SubresourceFilterIndexedRulesetTest, SimpleBlacklist) {
-  AddSimpleRule(UrlPattern("?param=", kSubstring), false);
+  ASSERT_TRUE(AddSimpleRule("?param="));
   Finish();
 
   EXPECT_TRUE(ShouldAllow("https://example.com"));
@@ -735,26 +616,25 @@
 }
 
 TEST_F(SubresourceFilterIndexedRulesetTest, SimpleWhitelist) {
-  AddSimpleRule(UrlPattern("example.com/?filtered_content=", kSubstring), true);
+  ASSERT_TRUE(AddSimpleWhitelistRule("example.com/?filter_out="));
   Finish();
 
-  EXPECT_TRUE(ShouldAllow("https://example.com?filtered_content=image1"));
+  EXPECT_TRUE(ShouldAllow("https://example.com?filter_out=true"));
 }
 
-TEST_F(SubresourceFilterIndexedRulesetTest, BlacklistWhitelist) {
-  AddSimpleRule(UrlPattern("?filter=", kSubstring), false);
-  AddSimpleRule(UrlPattern("whitelisted.com/?filter=", kSubstring), true);
+TEST_F(SubresourceFilterIndexedRulesetTest, SimpleBlacklistAndWhitelist) {
+  ASSERT_TRUE(AddSimpleRule("?filter="));
+  ASSERT_TRUE(AddSimpleWhitelistRule("whitelisted.com/?filter="));
   Finish();
 
-  EXPECT_TRUE(ShouldAllow("https://whitelisted.com?filter=off"));
-  EXPECT_TRUE(ShouldAllow("https://notblacklisted.com"));
   EXPECT_FALSE(ShouldAllow("http://blacklisted.com?filter=on"));
+  EXPECT_TRUE(ShouldAllow("https://whitelisted.com?filter=on"));
+  EXPECT_TRUE(ShouldAllow("https://notblacklisted.com"));
 }
 
 TEST_F(SubresourceFilterIndexedRulesetTest, BlacklistAndActivationType) {
-  AddSimpleRule(UrlPattern("example.com", kSubstring), false);
-  AddWhitelistRuleWithActivationTypes(UrlPattern("example.com", kSubstring),
-                                      kDocument);
+  ASSERT_TRUE(AddSimpleRule("example.com"));
+  ASSERT_TRUE(AddSimpleWhitelistRule("example.com", kDocument));
   Finish();
 
   EXPECT_TRUE(ShouldDeactivate("https://example.com", nullptr, kDocument));
@@ -763,7 +643,7 @@
   EXPECT_TRUE(ShouldAllow("https://xample.com"));
 }
 
-TEST_F(SubresourceFilterIndexedRulesetTest, RuleWithUnsupportedTypes) {
+TEST_F(SubresourceFilterIndexedRulesetTest, RulesWithUnsupportedTypes) {
   const struct {
     int element_types;
     int activation_types;
@@ -779,21 +659,24 @@
       {proto::ELEMENT_TYPE_POPUP, proto::ACTIVATION_TYPE_ELEMHIDE},
   };
 
-  for (const auto& rule : kRules) {
-    UrlRuleBuilder builder(UrlPattern("example.com"));
-    builder.rule().set_element_types(rule.element_types);
-    builder.rule().set_activation_types(rule.activation_types);
-    EXPECT_FALSE(indexer_.AddUrlRule(builder.rule()));
+  for (const auto& rule_data : kRules) {
+    auto rule = MakeUrlRule(UrlPattern("example.com", kSubstring));
+    rule.set_element_types(rule_data.element_types);
+    rule.set_activation_types(rule_data.activation_types);
+    EXPECT_FALSE(AddUrlRule(rule))
+        << "ElementTypes: " << static_cast<int>(rule_data.element_types)
+        << "; ActivationTypes: "
+        << static_cast<int>(rule_data.activation_types);
   }
-  AddSimpleRule(UrlPattern("exmpl.com", kSubstring), false);
-
+  ASSERT_TRUE(AddSimpleRule("exmpl.com"));
   Finish();
+
   EXPECT_TRUE(ShouldAllow("http://example.com/"));
   EXPECT_FALSE(ShouldAllow("https://exmpl.com/"));
 }
 
 TEST_F(SubresourceFilterIndexedRulesetTest,
-       RuleWithSupportedAndUnsupportedTypes) {
+       RulesWithSupportedAndUnsupportedTypes) {
   const struct {
     int element_types;
     int activation_types;
@@ -803,18 +686,22 @@
       {0, kDocument | (proto::ACTIVATION_TYPE_MAX << 1)},
   };
 
-  for (const auto& rule : kRules) {
-    UrlRuleBuilder builder(UrlPattern("example.com"));
-    builder.rule().set_element_types(rule.element_types);
-    builder.rule().set_activation_types(rule.activation_types);
-    if (rule.activation_types)
-      builder.rule().set_semantics(proto::RULE_SEMANTICS_WHITELIST);
-    EXPECT_TRUE(indexer_.AddUrlRule(builder.rule()));
+  for (const auto& rule_data : kRules) {
+    auto rule = MakeUrlRule(UrlPattern("example.com", kSubstring));
+    rule.set_element_types(rule_data.element_types);
+    rule.set_activation_types(rule_data.activation_types);
+    if (rule_data.activation_types)
+      rule.set_semantics(proto::RULE_SEMANTICS_WHITELIST);
+    EXPECT_TRUE(AddUrlRule(rule))
+        << "ElementTypes: " << static_cast<int>(rule_data.element_types)
+        << "; ActivationTypes: "
+        << static_cast<int>(rule_data.activation_types);
   }
   Finish();
 
   EXPECT_FALSE(ShouldAllow("http://example.com/", nullptr, kImage));
   EXPECT_FALSE(ShouldAllow("http://example.com/", nullptr, kScript));
+  EXPECT_TRUE(ShouldAllow("http://example.com/", nullptr, kPopup));
   EXPECT_TRUE(ShouldAllow("http://example.com/"));
 
   EXPECT_TRUE(ShouldDeactivate("http://example.com", nullptr, kDocument));
diff --git a/components/subresource_filter/core/common/url_pattern.cc b/components/subresource_filter/core/common/url_pattern.cc
index d601f41..381def4b 100644
--- a/components/subresource_filter/core/common/url_pattern.cc
+++ b/components/subresource_filter/core/common/url_pattern.cc
@@ -20,7 +20,7 @@
 #include <ostream>
 
 #include "base/logging.h"
-#include "components/subresource_filter/core/common/flat/rules_generated.h"
+#include "components/subresource_filter/core/common/flat/url_pattern_index_generated.h"
 #include "components/subresource_filter/core/common/fuzzy_pattern_matching.h"
 #include "components/subresource_filter/core/common/string_splitter.h"
 #include "url/gurl.h"
diff --git a/components/subresource_filter/core/common/url_pattern_index.cc b/components/subresource_filter/core/common/url_pattern_index.cc
new file mode 100644
index 0000000..49c3cb9
--- /dev/null
+++ b/components/subresource_filter/core/common/url_pattern_index.cc
@@ -0,0 +1,566 @@
+// Copyright 2017 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.
+
+#include "components/subresource_filter/core/common/url_pattern_index.h"
+
+#include <algorithm>
+#include <limits>
+#include <string>
+
+#include "base/logging.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/strings/string_util.h"
+#include "components/subresource_filter/core/common/ngram_extractor.h"
+#include "components/subresource_filter/core/common/url_pattern.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
+namespace subresource_filter {
+
+namespace {
+
+using FlatStringOffset = flatbuffers::Offset<flatbuffers::String>;
+using FlatDomains = flatbuffers::Vector<FlatStringOffset>;
+using FlatDomainsOffset = flatbuffers::Offset<FlatDomains>;
+
+base::StringPiece ToStringPiece(const flatbuffers::String* string) {
+  DCHECK(string);
+  return base::StringPiece(string->c_str(), string->size());
+}
+
+// Performs three-way comparison between two domains. In the total order defined
+// by this predicate, the lengths of domains will be monotonically decreasing.
+int CompareDomains(base::StringPiece lhs_domain, base::StringPiece rhs_domain) {
+  if (lhs_domain.size() != rhs_domain.size())
+    return lhs_domain.size() > rhs_domain.size() ? -1 : 1;
+  return lhs_domain.compare(rhs_domain);
+}
+
+bool HasNoUpperAscii(base::StringPiece string) {
+  return std::none_of(string.begin(), string.end(),
+                      [](char c) { return base::IsAsciiUpper(c); });
+}
+
+// Checks whether a URL |rule| can be converted to its FlatBuffers equivalent,
+// and performs the actual conversion.
+class UrlRuleFlatBufferConverter {
+ public:
+  // Creates the converter, and initializes |is_convertible| bit. If
+  // |is_convertible| == true, then all the fields, needed for serializing the
+  // |rule| to FlatBuffer, are initialized (|options|, |anchor_right|, etc.).
+  explicit UrlRuleFlatBufferConverter(const proto::UrlRule& rule)
+      : rule_(rule) {
+    is_convertible_ = InitializeOptions() && InitializeElementTypes() &&
+                      InitializeActivationTypes() && InitializeUrlPattern() &&
+                      IsMeaningful();
+  }
+
+  // Returns whether the |rule| can be converted to its FlatBuffers equivalent.
+  // The conversion is not possible if the rule has attributes not supported by
+  // this client version.
+  bool is_convertible() const { return is_convertible_; }
+
+  // Writes the URL |rule| to the FlatBuffer using the |builder|, and returns
+  // the offset to the serialized rule.
+  UrlRuleOffset SerializeConvertedRule(
+      flatbuffers::FlatBufferBuilder* builder) const {
+    DCHECK(is_convertible());
+
+    FlatDomainsOffset domains_included_offset;
+    FlatDomainsOffset domains_excluded_offset;
+    if (rule_.domains_size()) {
+      // TODO(pkalinnikov): Consider sharing the vectors between rules.
+      std::vector<FlatStringOffset> domains_included;
+      std::vector<FlatStringOffset> domains_excluded;
+      // Reserve only for |domains_included| because it is expected to be the
+      // one used more frequently.
+      domains_included.reserve(rule_.domains_size());
+
+      for (const auto& domain_list_item : rule_.domains()) {
+        // Note: The |domain| can have non-ASCII UTF-8 characters, but
+        // ToLowerASCII leaves these intact.
+        // TODO(pkalinnikov): Convert non-ASCII characters to lower case too.
+        // TODO(pkalinnikov): Possibly convert Punycode to IDN here or directly
+        // assume this is done in the proto::UrlRule.
+        const std::string& domain = domain_list_item.domain();
+        auto offset = builder->CreateSharedString(
+            HasNoUpperAscii(domain) ? domain : base::ToLowerASCII(domain));
+
+        if (domain_list_item.exclude())
+          domains_excluded.push_back(offset);
+        else
+          domains_included.push_back(offset);
+      }
+
+      // The comparator ensuring the domains order necessary for fast matching.
+      auto precedes = [&builder](FlatStringOffset lhs, FlatStringOffset rhs) {
+        return CompareDomains(ToStringPiece(flatbuffers::GetTemporaryPointer(
+                                  *builder, lhs)),
+                              ToStringPiece(flatbuffers::GetTemporaryPointer(
+                                  *builder, rhs))) < 0;
+      };
+
+      // The domains are stored in sorted order to support fast matching.
+      if (!domains_included.empty()) {
+        // TODO(pkalinnikov): Don't sort if it is already sorted offline.
+        std::sort(domains_included.begin(), domains_included.end(), precedes);
+        domains_included_offset = builder->CreateVector(domains_included);
+      }
+      if (!domains_excluded.empty()) {
+        std::sort(domains_excluded.begin(), domains_excluded.end(), precedes);
+        domains_excluded_offset = builder->CreateVector(domains_excluded);
+      }
+    }
+
+    auto url_pattern_offset = builder->CreateString(rule_.url_pattern());
+
+    return flat::CreateUrlRule(
+        *builder, options_, element_types_, activation_types_,
+        url_pattern_type_, anchor_left_, anchor_right_, domains_included_offset,
+        domains_excluded_offset, url_pattern_offset);
+  }
+
+ private:
+  static bool ConvertAnchorType(proto::AnchorType anchor_type,
+                                flat::AnchorType* result) {
+    switch (anchor_type) {
+      case proto::ANCHOR_TYPE_NONE:
+        *result = flat::AnchorType_NONE;
+        break;
+      case proto::ANCHOR_TYPE_BOUNDARY:
+        *result = flat::AnchorType_BOUNDARY;
+        break;
+      case proto::ANCHOR_TYPE_SUBDOMAIN:
+        *result = flat::AnchorType_SUBDOMAIN;
+        break;
+      default:
+        return false;  // Unsupported anchor type.
+    }
+    return true;
+  }
+
+  bool InitializeOptions() {
+    if (rule_.semantics() == proto::RULE_SEMANTICS_WHITELIST) {
+      options_ |= flat::OptionFlag_IS_WHITELIST;
+    } else if (rule_.semantics() != proto::RULE_SEMANTICS_BLACKLIST) {
+      return false;  // Unsupported semantics.
+    }
+
+    switch (rule_.source_type()) {
+      case proto::SOURCE_TYPE_ANY:
+        options_ |= flat::OptionFlag_APPLIES_TO_THIRD_PARTY;
+      // Note: fall through here intentionally.
+      case proto::SOURCE_TYPE_FIRST_PARTY:
+        options_ |= flat::OptionFlag_APPLIES_TO_FIRST_PARTY;
+        break;
+      case proto::SOURCE_TYPE_THIRD_PARTY:
+        options_ |= flat::OptionFlag_APPLIES_TO_THIRD_PARTY;
+        break;
+
+      default:
+        return false;  // Unsupported source type.
+    }
+
+    if (rule_.match_case())
+      options_ |= flat::OptionFlag_IS_MATCH_CASE;
+
+    return true;
+  }
+
+  bool InitializeElementTypes() {
+    static_assert(
+        proto::ELEMENT_TYPE_ALL <= std::numeric_limits<uint16_t>::max(),
+        "Element types can not be stored in uint16_t.");
+    element_types_ = static_cast<uint16_t>(rule_.element_types());
+
+    // Note: Normally we can not distinguish between the main plugin resource
+    // and any other loads it makes. We treat them both as OBJECT requests.
+    if (element_types_ & proto::ELEMENT_TYPE_OBJECT_SUBREQUEST)
+      element_types_ |= proto::ELEMENT_TYPE_OBJECT;
+
+    // Ignore unknown element types.
+    element_types_ &= proto::ELEMENT_TYPE_ALL;
+    // Filtering popups is not supported.
+    element_types_ &= ~proto::ELEMENT_TYPE_POPUP;
+
+    return true;
+  }
+
+  bool InitializeActivationTypes() {
+    static_assert(
+        proto::ACTIVATION_TYPE_ALL <= std::numeric_limits<uint8_t>::max(),
+        "Activation types can not be stored in uint8_t.");
+    activation_types_ = static_cast<uint8_t>(rule_.activation_types());
+
+    // Only the following activation types are supported, ignore the others.
+    activation_types_ &=
+        proto::ACTIVATION_TYPE_DOCUMENT | proto::ACTIVATION_TYPE_GENERICBLOCK;
+
+    return true;
+  }
+
+  bool InitializeUrlPattern() {
+    switch (rule_.url_pattern_type()) {
+      case proto::URL_PATTERN_TYPE_SUBSTRING:
+        url_pattern_type_ = flat::UrlPatternType_SUBSTRING;
+        break;
+      case proto::URL_PATTERN_TYPE_WILDCARDED:
+        url_pattern_type_ = flat::UrlPatternType_WILDCARDED;
+        break;
+
+      // TODO(pkalinnikov): Implement REGEXP rules matching.
+      case proto::URL_PATTERN_TYPE_REGEXP:
+      default:
+        return false;  // Unsupported URL pattern type.
+    }
+
+    if (!ConvertAnchorType(rule_.anchor_left(), &anchor_left_) ||
+        !ConvertAnchorType(rule_.anchor_right(), &anchor_right_)) {
+      return false;
+    }
+    if (anchor_right_ == flat::AnchorType_SUBDOMAIN)
+      return false;  // Unsupported right anchor.
+
+    return true;
+  }
+
+  // Returns whether the rule is not a no-op after all the modifications above.
+  bool IsMeaningful() const { return element_types_ || activation_types_; }
+
+  const proto::UrlRule& rule_;
+
+  uint8_t options_ = 0;
+  uint16_t element_types_ = 0;
+  uint8_t activation_types_ = 0;
+  flat::UrlPatternType url_pattern_type_ = flat::UrlPatternType_WILDCARDED;
+  flat::AnchorType anchor_left_ = flat::AnchorType_NONE;
+  flat::AnchorType anchor_right_ = flat::AnchorType_NONE;
+
+  bool is_convertible_ = true;
+};
+
+}  // namespace
+
+// Helpers. --------------------------------------------------------------------
+
+UrlRuleOffset SerializeUrlRule(const proto::UrlRule& rule,
+                               flatbuffers::FlatBufferBuilder* builder) {
+  DCHECK(builder);
+  UrlRuleFlatBufferConverter converter(rule);
+  if (!converter.is_convertible())
+    return UrlRuleOffset();
+  DCHECK_NE(rule.url_pattern_type(), proto::URL_PATTERN_TYPE_REGEXP);
+  return converter.SerializeConvertedRule(builder);
+}
+
+// UrlPatternIndexBuilder ------------------------------------------------------
+
+UrlPatternIndexBuilder::UrlPatternIndexBuilder(
+    flatbuffers::FlatBufferBuilder* flat_builder)
+    : flat_builder_(flat_builder) {
+  DCHECK(flat_builder_);
+}
+
+UrlPatternIndexBuilder::~UrlPatternIndexBuilder() = default;
+
+void UrlPatternIndexBuilder::IndexUrlRule(UrlRuleOffset offset) {
+  DCHECK(offset.o);
+
+  const auto* rule = flatbuffers::GetTemporaryPointer(*flat_builder_, offset);
+  DCHECK(rule);
+  NGram ngram = GetMostDistinctiveNGram(ToStringPiece(rule->url_pattern()));
+
+  if (ngram) {
+    ngram_index_[ngram].push_back(offset);
+  } else {
+    // TODO(pkalinnikov): Index fallback rules as well.
+    fallback_rules_.push_back(offset);
+  }
+}
+
+UrlPatternIndexOffset UrlPatternIndexBuilder::Finish() {
+  std::vector<flatbuffers::Offset<flat::NGramToRules>> flat_hash_table(
+      ngram_index_.table_size());
+
+  flatbuffers::Offset<flat::NGramToRules> empty_slot_offset =
+      flat::CreateNGramToRules(*flat_builder_);
+  for (size_t i = 0, size = ngram_index_.table_size(); i != size; ++i) {
+    const uint32_t entry_index = ngram_index_.hash_table()[i];
+    if (entry_index >= ngram_index_.size()) {
+      flat_hash_table[i] = empty_slot_offset;
+      continue;
+    }
+    const MutableNGramIndex::EntryType& entry =
+        ngram_index_.entries()[entry_index];
+    auto rules_offset = flat_builder_->CreateVector(entry.second);
+    flat_hash_table[i] =
+        flat::CreateNGramToRules(*flat_builder_, entry.first, rules_offset);
+  }
+  auto ngram_index_offset = flat_builder_->CreateVector(flat_hash_table);
+
+  auto fallback_rules_offset = flat_builder_->CreateVector(fallback_rules_);
+
+  return flat::CreateUrlPatternIndex(*flat_builder_, kNGramSize,
+                                     ngram_index_offset, empty_slot_offset,
+                                     fallback_rules_offset);
+}
+
+NGram UrlPatternIndexBuilder::GetMostDistinctiveNGram(
+    base::StringPiece pattern) {
+  size_t min_list_size = std::numeric_limits<size_t>::max();
+  NGram best_ngram = 0;
+
+  auto ngrams = CreateNGramExtractor<kNGramSize, NGram>(
+      pattern, [](char c) { return c == '*' || c == '^'; });
+
+  for (uint64_t ngram : ngrams) {
+    const MutableUrlRuleList* rules = ngram_index_.Get(ngram);
+    const size_t list_size = rules ? rules->size() : 0;
+    if (list_size < min_list_size) {
+      // TODO(pkalinnikov): Pick random of the same-sized lists.
+      min_list_size = list_size;
+      best_ngram = ngram;
+      if (list_size == 0)
+        break;
+    }
+  }
+
+  return best_ngram;
+}
+
+// UrlPatternIndex -------------------------------------------------------------
+
+namespace {
+
+using FlatUrlRuleList = flatbuffers::Vector<flatbuffers::Offset<flat::UrlRule>>;
+using FlatNGramIndex =
+    flatbuffers::Vector<flatbuffers::Offset<flat::NGramToRules>>;
+
+// Returns the size of the longest (sub-)domain of |origin| matching one of the
+// |domains| in the list.
+//
+// The |domains| should be sorted in descending order of their length, and
+// ascending alphabetical order within the groups of same-length domains.
+size_t GetLongestMatchingSubdomain(const url::Origin& origin,
+                                   const FlatDomains& domains) {
+  // If the |domains| list is short, then the simple strategy is usually faster.
+  if (domains.size() <= 5) {
+    for (auto* domain : domains) {
+      const base::StringPiece domain_piece = ToStringPiece(domain);
+      if (origin.DomainIs(domain_piece))
+        return domain_piece.size();
+    }
+    return 0;
+  }
+  // Otherwise look for each subdomain of the |origin| using binary search.
+
+  DCHECK(!origin.unique());
+  base::StringPiece canonicalized_host(origin.host());
+  if (canonicalized_host.empty())
+    return 0;
+
+  // If the host name ends with a dot, then ignore it.
+  if (canonicalized_host.back() == '.')
+    canonicalized_host.remove_suffix(1);
+
+  // The |left| bound of the search is shared between iterations, because
+  // subdomains are considered in decreasing order of their lengths, therefore
+  // each consecutive lower_bound will be at least as far as the previous.
+  flatbuffers::uoffset_t left = 0;
+  for (size_t position = 0;; ++position) {
+    const base::StringPiece subdomain = canonicalized_host.substr(position);
+
+    flatbuffers::uoffset_t right = domains.size();
+    while (left + 1 < right) {
+      auto middle = left + (right - left) / 2;
+      DCHECK_LT(middle, domains.size());
+      if (CompareDomains(ToStringPiece(domains[middle]), subdomain) <= 0)
+        left = middle;
+      else
+        right = middle;
+    }
+
+    DCHECK_LT(left, domains.size());
+    if (ToStringPiece(domains[left]) == subdomain)
+      return subdomain.size();
+
+    position = canonicalized_host.find('.', position);
+    if (position == base::StringPiece::npos)
+      break;
+  }
+
+  return 0;
+}
+
+// Returns whether the |origin| matches the domain list of the |rule|. A match
+// means that the longest domain in |domains| that |origin| is a sub-domain of
+// is not an exception OR all the |domains| are exceptions and neither matches
+// the |origin|. Thus, domain filters with more domain components trump filters
+// with fewer domain components, i.e. the more specific a filter is, the higher
+// the priority.
+//
+// A rule whose domain list is empty or contains only negative domains is still
+// considered a "generic" rule. Therefore, if |disable_generic_rules| is set,
+// this function will always return false for such rules.
+bool DoesOriginMatchDomainList(const url::Origin& origin,
+                               const flat::UrlRule& rule,
+                               bool disable_generic_rules) {
+  const bool is_generic = !rule.domains_included();
+  DCHECK(is_generic || rule.domains_included()->size());
+  if (disable_generic_rules && is_generic)
+    return false;
+
+  // Unique |origin| matches lists of exception domains only.
+  if (origin.unique())
+    return is_generic;
+
+  size_t longest_matching_included_domain_length = 1;
+  if (!is_generic) {
+    longest_matching_included_domain_length =
+        GetLongestMatchingSubdomain(origin, *rule.domains_included());
+  }
+  if (longest_matching_included_domain_length && rule.domains_excluded()) {
+    return GetLongestMatchingSubdomain(origin, *rule.domains_excluded()) <
+           longest_matching_included_domain_length;
+  }
+  return !!longest_matching_included_domain_length;
+}
+
+// Returns whether the request matches flags of the specified URL |rule|. Takes
+// into account:
+//  - |element_type| of the requested resource, if not *_UNSPECIFIED.
+//  - |activation_type| for a subdocument request, if not *_UNSPECIFIED.
+//  - Whether the resource |is_third_party| w.r.t. its embedding document.
+bool DoesRuleFlagsMatch(const flat::UrlRule& rule,
+                        proto::ElementType element_type,
+                        proto::ActivationType activation_type,
+                        bool is_third_party) {
+  DCHECK((element_type == proto::ELEMENT_TYPE_UNSPECIFIED) !=
+         (activation_type == proto::ACTIVATION_TYPE_UNSPECIFIED));
+
+  if (element_type != proto::ELEMENT_TYPE_UNSPECIFIED &&
+      !(rule.element_types() & element_type)) {
+    return false;
+  }
+  if (activation_type != proto::ACTIVATION_TYPE_UNSPECIFIED &&
+      !(rule.activation_types() & activation_type)) {
+    return false;
+  }
+
+  if (is_third_party &&
+      !(rule.options() & flat::OptionFlag_APPLIES_TO_THIRD_PARTY)) {
+    return false;
+  }
+  if (!is_third_party &&
+      !(rule.options() & flat::OptionFlag_APPLIES_TO_FIRST_PARTY)) {
+    return false;
+  }
+
+  return true;
+}
+
+const flat::UrlRule* FindMatchAmongCandidates(
+    const FlatUrlRuleList* candidates,
+    const GURL& url,
+    const url::Origin& document_origin,
+    proto::ElementType element_type,
+    proto::ActivationType activation_type,
+    bool is_third_party,
+    bool disable_generic_rules) {
+  if (!candidates)
+    return nullptr;
+  for (const flat::UrlRule* rule : *candidates) {
+    DCHECK_NE(rule, nullptr);
+    DCHECK_NE(rule->url_pattern_type(), flat::UrlPatternType_REGEXP);
+    if (!DoesRuleFlagsMatch(*rule, element_type, activation_type,
+                            is_third_party)) {
+      continue;
+    }
+    if (!UrlPattern(*rule).MatchesUrl(url))
+      continue;
+
+    if (DoesOriginMatchDomainList(document_origin, *rule,
+                                  disable_generic_rules)) {
+      return rule;
+    }
+  }
+
+  return nullptr;
+}
+
+// Returns whether the network request matches a UrlPattern |index| represented
+// in its FlatBuffers format. |is_third_party| should reflect the relation
+// between |url| and |document_origin|.
+const flat::UrlRule* FindMatchInFlatUrlPatternIndex(
+    const flat::UrlPatternIndex& index,
+    const GURL& url,
+    const url::Origin& document_origin,
+    proto::ElementType element_type,
+    proto::ActivationType activation_type,
+    bool is_third_party,
+    bool disable_generic_rules) {
+  const FlatNGramIndex* hash_table = index.ngram_index();
+  const flat::NGramToRules* empty_slot = index.ngram_index_empty_slot();
+  DCHECK_NE(hash_table, nullptr);
+
+  NGramHashTableProber prober;
+
+  auto ngrams = CreateNGramExtractor<kNGramSize, uint64_t>(
+      url.spec(), [](char) { return false; });
+  for (uint64_t ngram : ngrams) {
+    const size_t slot_index = prober.FindSlot(
+        ngram, base::strict_cast<size_t>(hash_table->size()),
+        [hash_table, empty_slot](NGram ngram, size_t slot_index) {
+          const flat::NGramToRules* entry = hash_table->Get(slot_index);
+          DCHECK_NE(entry, nullptr);
+          return entry == empty_slot || entry->ngram() == ngram;
+        });
+    DCHECK_LT(slot_index, hash_table->size());
+
+    const flat::NGramToRules* entry = hash_table->Get(slot_index);
+    if (entry == empty_slot)
+      continue;
+    const flat::UrlRule* rule = FindMatchAmongCandidates(
+        entry->rule_list(), url, document_origin, element_type, activation_type,
+        is_third_party, disable_generic_rules);
+    if (rule)
+      return rule;
+  }
+
+  const FlatUrlRuleList* rules = index.fallback_rules();
+  return FindMatchAmongCandidates(rules, url, document_origin, element_type,
+                                  activation_type, is_third_party,
+                                  disable_generic_rules);
+}
+
+}  // namespace
+
+UrlPatternIndexMatcher::UrlPatternIndexMatcher(
+    const flat::UrlPatternIndex* flat_index)
+    : flat_index_(flat_index) {
+  DCHECK(!flat_index || flat_index->n() == kNGramSize);
+}
+
+UrlPatternIndexMatcher::~UrlPatternIndexMatcher() = default;
+
+const flat::UrlRule* UrlPatternIndexMatcher::FindMatch(
+    const GURL& url,
+    const url::Origin& first_party_origin,
+    proto::ElementType element_type,
+    proto::ActivationType activation_type,
+    bool is_third_party,
+    bool disable_generic_rules) const {
+  if (!flat_index_ || !url.is_valid())
+    return nullptr;
+  if ((element_type == proto::ELEMENT_TYPE_UNSPECIFIED) ==
+      (activation_type == proto::ACTIVATION_TYPE_UNSPECIFIED)) {
+    return nullptr;
+  }
+
+  return FindMatchInFlatUrlPatternIndex(*flat_index_, url, first_party_origin,
+                                        element_type, activation_type,
+                                        is_third_party, disable_generic_rules);
+}
+
+}  // namespace subresource_filter
diff --git a/components/subresource_filter/core/common/url_pattern_index.h b/components/subresource_filter/core/common/url_pattern_index.h
new file mode 100644
index 0000000..adeade6
--- /dev/null
+++ b/components/subresource_filter/core/common/url_pattern_index.h
@@ -0,0 +1,135 @@
+// Copyright 2017 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.
+
+#ifndef COMPONENTS_SUBRESOURCE_FILTER_CORE_COMMON_URL_PATTERN_INDEX_H_
+#define COMPONENTS_SUBRESOURCE_FILTER_CORE_COMMON_URL_PATTERN_INDEX_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <vector>
+
+#include "base/macros.h"
+#include "base/strings/string_piece.h"
+#include "components/subresource_filter/core/common/closed_hash_map.h"
+#include "components/subresource_filter/core/common/flat/url_pattern_index_generated.h"
+#include "components/subresource_filter/core/common/proto/rules.pb.h"
+#include "components/subresource_filter/core/common/uint64_hasher.h"
+#include "third_party/flatbuffers/src/include/flatbuffers/flatbuffers.h"
+
+class GURL;
+
+namespace url {
+class Origin;
+}
+
+namespace subresource_filter {
+
+// The integer type used to represent N-grams.
+using NGram = uint64_t;
+// The hasher used for hashing N-grams.
+using NGramHasher = Uint64Hasher;
+// The hash table probe sequence used both by UrlPatternIndex and its builder.
+using NGramHashTableProber = DefaultProber<NGram, NGramHasher>;
+
+// FlatBuffer offset aliases.
+using UrlRuleOffset = flatbuffers::Offset<flat::UrlRule>;
+using UrlPatternIndexOffset = flatbuffers::Offset<flat::UrlPatternIndex>;
+
+constexpr size_t kNGramSize = 5;
+static_assert(kNGramSize <= sizeof(NGram), "NGram type is too narrow.");
+
+// Serializes the |rule| to the FlatBuffer |builder|, and returns an offset to
+// it in the resulting buffer. Returns null offset iff the |rule| could not be
+// serialized because of unsupported options or it is otherwise invalid.
+UrlRuleOffset SerializeUrlRule(const proto::UrlRule& rule,
+                               flatbuffers::FlatBufferBuilder* builder);
+
+// The class used to construct an index over the URL patterns of a set of URL
+// rules. The rules themselves need to be converted to FlatBuffers format by the
+// client of this class, as well as persisted into the |flat_builder| that is
+// supplied in the constructor.
+class UrlPatternIndexBuilder {
+ public:
+  explicit UrlPatternIndexBuilder(flatbuffers::FlatBufferBuilder* flat_builder);
+  ~UrlPatternIndexBuilder();
+
+  // Adds a UrlRule to the index. The caller should have already persisted the
+  // rule into the same |flat_builder| by a call to SerializeUrlRule returning a
+  // non-null |offset|, and should pass in the resulting |offset| here.
+  void IndexUrlRule(UrlRuleOffset offset);
+
+  // Finalizes construction of the index, serializes it using |flat_builder|,
+  // and returns an offset to it in the resulting FlatBuffer.
+  UrlPatternIndexOffset Finish();
+
+ private:
+  using MutableUrlRuleList = std::vector<UrlRuleOffset>;
+  using MutableNGramIndex =
+      ClosedHashMap<NGram, MutableUrlRuleList, NGramHashTableProber>;
+
+  // Returns an N-gram of the |pattern| encoded into the NGram integer type. The
+  // N-gram is picked using a greedy heuristic, i.e. the one is chosen which
+  // corresponds to the shortest list of rules within the index. If there are no
+  // valid N-grams in the |pattern|, the return value is 0.
+  NGram GetMostDistinctiveNGram(base::StringPiece pattern);
+
+  // This index contains all non-REGEXP rules that have at least one acceptable
+  // N-gram. For each given rule, the N-gram used as an index key is picked
+  // greedily (see GetMostDistinctiveNGram).
+  MutableNGramIndex ngram_index_;
+
+  // A fallback list that contains all the rules with no acceptable N-gram.
+  MutableUrlRuleList fallback_rules_;
+
+  // Must outlive this instance.
+  flatbuffers::FlatBufferBuilder* flat_builder_;
+
+  DISALLOW_COPY_AND_ASSIGN(UrlPatternIndexBuilder);
+};
+
+// Encapsulates a read-only index built over the URL patterns of a set of URL
+// rules, and provides fast matching of network requests against these rules.
+class UrlPatternIndexMatcher {
+ public:
+  // Creates an instance to access the given |flat_index|. If |flat_index| is
+  // nullptr, then all requests return no match.
+  explicit UrlPatternIndexMatcher(const flat::UrlPatternIndex* flat_index);
+  ~UrlPatternIndexMatcher();
+
+  // If the index contains one or more UrlRules that match the request, returns
+  // one of them (it is undefined which one). Otherwise, returns nullptr.
+  //
+  // Notes on parameters:
+  //  - |url| should be valid, otherwise the return value is nullptr.
+  //  - Exactly one of |element_type| and |activation_type| should be specified,
+  //    i.e., not equal to *_UNSPECIFIED, otherwise the return value is nullptr.
+  //  - |is_third_party| should be pre-computed by the caller, e.g. using the
+  //    registry_controlled_domains library, to reflect the relation between
+  //    |url| and |first_party_origin|.
+  //
+  // A rule is deemed to match the request iff all of the following applies:
+  //  - The |url| matches the rule's UrlPattern (see url_pattern.h).
+  //  - The |first_party_origin| matches the rule's targeted domains list.
+  //  - |element_type| or |activation_type| is among the rule's targeted types.
+  //  - The |is_third_party| bit matches the rule's requirement on the requested
+  //    |url| being first-/third-party w.r.t. its |first_party_origin|.
+  //  - The rule is not generic if |disable_generic_rules| is true.
+  const flat::UrlRule* FindMatch(const GURL& url,
+                                 const url::Origin& first_party_origin,
+                                 proto::ElementType element_type,
+                                 proto::ActivationType activation_type,
+                                 bool is_third_party,
+                                 bool disable_generic_rules) const;
+
+ private:
+  // Must outlive this instance.
+  const flat::UrlPatternIndex* flat_index_;
+
+  DISALLOW_COPY_AND_ASSIGN(UrlPatternIndexMatcher);
+};
+
+}  // namespace subresource_filter
+
+#endif  // COMPONENTS_SUBRESOURCE_FILTER_CORE_COMMON_URL_PATTERN_INDEX_H_
diff --git a/components/subresource_filter/core/common/url_rule_test_support.cc b/components/subresource_filter/core/common/url_rule_test_support.cc
new file mode 100644
index 0000000..ab6f7f1b
--- /dev/null
+++ b/components/subresource_filter/core/common/url_rule_test_support.cc
@@ -0,0 +1,56 @@
+// Copyright 2017 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.
+
+#include "components/subresource_filter/core/common/url_rule_test_support.h"
+
+#include "base/logging.h"
+#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
+namespace subresource_filter {
+namespace testing {
+
+proto::UrlRule MakeUrlRule(const UrlPattern& url_pattern) {
+  proto::UrlRule rule;
+
+  rule.set_semantics(proto::RULE_SEMANTICS_BLACKLIST);
+  rule.set_source_type(proto::SOURCE_TYPE_ANY);
+  rule.set_element_types(kAllElementTypes);
+
+  rule.set_url_pattern_type(url_pattern.type());
+  rule.set_anchor_left(url_pattern.anchor_left());
+  rule.set_anchor_right(url_pattern.anchor_right());
+  rule.set_match_case(url_pattern.match_case());
+  rule.set_url_pattern(url_pattern.url_pattern().as_string());
+
+  return rule;
+}
+
+void AddDomains(const std::vector<std::string>& domains, proto::UrlRule* rule) {
+  for (std::string domain_pattern : domains) {
+    DCHECK(!domain_pattern.empty());
+    auto* domain = rule->add_domains();
+    if (domain_pattern[0] == '~') {
+      domain_pattern.erase(0, 1);
+      domain->set_exclude(true);
+    }
+    domain->set_domain(std::move(domain_pattern));
+  }
+}
+
+url::Origin GetOrigin(base::StringPiece origin_string) {
+  return !origin_string.empty() ? url::Origin(GURL(origin_string))
+                                : url::Origin();
+}
+
+bool IsThirdParty(const GURL& url, const url::Origin& first_party_origin) {
+  return first_party_origin.unique() ||
+         !net::registry_controlled_domains::SameDomainOrHost(
+             url, first_party_origin,
+             net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
+}
+
+}  // namespace testing
+}  // namespace subresource_filter
diff --git a/components/subresource_filter/core/common/url_rule_test_support.h b/components/subresource_filter/core/common/url_rule_test_support.h
new file mode 100644
index 0000000..fef73b9
--- /dev/null
+++ b/components/subresource_filter/core/common/url_rule_test_support.h
@@ -0,0 +1,74 @@
+// Copyright 2017 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.
+
+#ifndef COMPONENTS_SUBRESOURCE_FILTER_CORE_COMMON_URL_RULE_TEST_SUPPORT_H_
+#define COMPONENTS_SUBRESOURCE_FILTER_CORE_COMMON_URL_RULE_TEST_SUPPORT_H_
+
+#include <string>
+#include <vector>
+
+#include "base/strings/string_piece.h"
+#include "components/subresource_filter/core/common/proto/rules.pb.h"
+#include "components/subresource_filter/core/common/url_pattern.h"
+
+class GURL;
+
+namespace url {
+class Origin;
+}
+
+namespace subresource_filter {
+namespace testing {
+
+// Constants -------------------------------------------------------------------
+
+constexpr proto::UrlPatternType kSubstring = proto::URL_PATTERN_TYPE_SUBSTRING;
+
+constexpr proto::AnchorType kAnchorNone = proto::ANCHOR_TYPE_NONE;
+constexpr proto::AnchorType kBoundary = proto::ANCHOR_TYPE_BOUNDARY;
+constexpr proto::AnchorType kSubdomain = proto::ANCHOR_TYPE_SUBDOMAIN;
+
+constexpr proto::ElementType kNoElement = proto::ELEMENT_TYPE_UNSPECIFIED;
+constexpr proto::ElementType kOther = proto::ELEMENT_TYPE_OTHER;
+constexpr proto::ElementType kScript = proto::ELEMENT_TYPE_SCRIPT;
+constexpr proto::ElementType kImage = proto::ELEMENT_TYPE_IMAGE;
+constexpr proto::ElementType kSubdocument = proto::ELEMENT_TYPE_SUBDOCUMENT;
+constexpr proto::ElementType kFont = proto::ELEMENT_TYPE_FONT;
+constexpr proto::ElementType kPopup = proto::ELEMENT_TYPE_POPUP;
+constexpr proto::ElementType kWebSocket = proto::ELEMENT_TYPE_WEBSOCKET;
+constexpr proto::ElementType kAllElementTypes = proto::ELEMENT_TYPE_ALL;
+
+constexpr proto::ActivationType kNoActivation =
+    proto::ACTIVATION_TYPE_UNSPECIFIED;
+constexpr proto::ActivationType kDocument = proto::ACTIVATION_TYPE_DOCUMENT;
+constexpr proto::ActivationType kGenericBlock =
+    proto::ACTIVATION_TYPE_GENERICBLOCK;
+
+constexpr proto::SourceType kAnyParty = proto::SOURCE_TYPE_ANY;
+constexpr proto::SourceType kThirdParty = proto::SOURCE_TYPE_THIRD_PARTY;
+constexpr proto::SourceType kFirstParty = proto::SOURCE_TYPE_FIRST_PARTY;
+
+// Helpers ---------------------------------------------------------------------
+
+// Creates a UrlRule with the given |url_pattern|, and all necessary fields
+// initialized to defaults.
+proto::UrlRule MakeUrlRule(const UrlPattern& url_pattern = UrlPattern());
+
+// Parses |domains| and adds them to the domain list of the |rule|.
+//
+// The |domains| vector should contain non-empty strings. If a string starts
+// with '~' then the following part of the string is an exception domain.
+void AddDomains(const std::vector<std::string>& domains, proto::UrlRule* rule);
+
+// Returns the url::Origin parsed from |origin_string|, or the unique origin if
+// the string is empty.
+url::Origin GetOrigin(base::StringPiece origin_string);
+
+// Returns whether |url| is third-party resource w.r.t. |first_party_origin|.
+bool IsThirdParty(const GURL& url, const url::Origin& first_party_origin);
+
+}  // namespace testing
+}  // namespace subresource_filter
+
+#endif  // COMPONENTS_SUBRESOURCE_FILTER_CORE_COMMON_URL_RULE_TEST_SUPPORT_H_
diff --git a/components/suggestions/BUILD.gn b/components/suggestions/BUILD.gn
index 9d4a692..6d93d04 100644
--- a/components/suggestions/BUILD.gn
+++ b/components/suggestions/BUILD.gn
@@ -6,6 +6,8 @@
   sources = [
     "blacklist_store.cc",
     "blacklist_store.h",
+    "features.cc",
+    "features.h",
     "image_encoder.h",
     "image_manager.cc",
     "image_manager.h",
diff --git a/components/suggestions/features.cc b/components/suggestions/features.cc
new file mode 100644
index 0000000..2cac42d4
--- /dev/null
+++ b/components/suggestions/features.cc
@@ -0,0 +1,12 @@
+// Copyright 2017 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.
+
+#include "components/suggestions/features.h"
+
+namespace suggestions {
+
+const base::Feature kUseSuggestionsEvenIfFewFeature{
+    "UseSuggestionsEvenIfFew", base::FEATURE_DISABLED_BY_DEFAULT};
+
+}  // namespace suggestions
diff --git a/components/suggestions/features.h b/components/suggestions/features.h
new file mode 100644
index 0000000..8a794594
--- /dev/null
+++ b/components/suggestions/features.h
@@ -0,0 +1,18 @@
+// Copyright 2017 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.
+
+#ifndef COMPONENTS_SUGGESTIONS_FEATURES_H_
+#define COMPONENTS_SUGGESTIONS_FEATURES_H_
+
+#include "base/feature_list.h"
+
+namespace suggestions {
+
+// If this feature is enabled, we request and use suggestions even if there are
+// only very few of them.
+extern const base::Feature kUseSuggestionsEvenIfFewFeature;
+
+}  // namespace suggestions
+
+#endif  // COMPONENTS_SUGGESTIONS_FEATURES_H_
diff --git a/content/browser/payments/payment_app_database.cc b/content/browser/payments/payment_app_database.cc
index 4a4644f..00e79cf 100644
--- a/content/browser/payments/payment_app_database.cc
+++ b/content/browser/payments/payment_app_database.cc
@@ -203,6 +203,19 @@
           base::Passed(std::move(callback))));
 }
 
+void PaymentAppDatabase::ClearPaymentInstruments(
+    const GURL& scope,
+    ClearPaymentInstrumentsCallback callback) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+
+  service_worker_context_->FindReadyRegistrationForPattern(
+      scope,
+      base::Bind(
+          &PaymentAppDatabase::DidFindRegistrationToClearPaymentInstruments,
+          weak_ptr_factory_.GetWeakPtr(), scope,
+          base::Passed(std::move(callback))));
+}
+
 void PaymentAppDatabase::DidFindRegistrationToWriteManifest(
     payments::mojom::PaymentAppManifestPtr manifest,
     const WriteManifestCallback& callback,
@@ -525,4 +538,57 @@
           : PaymentHandlerStatus::STORAGE_OPERATION_FAILED);
 }
 
+void PaymentAppDatabase::DidFindRegistrationToClearPaymentInstruments(
+    const GURL& scope,
+    ClearPaymentInstrumentsCallback callback,
+    ServiceWorkerStatusCode status,
+    scoped_refptr<ServiceWorkerRegistration> registration) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+
+  if (status != SERVICE_WORKER_OK) {
+    std::move(callback).Run(PaymentHandlerStatus::NO_ACTIVE_WORKER);
+    return;
+  }
+
+  KeysOfPaymentInstruments(
+      scope,
+      base::BindOnce(&PaymentAppDatabase::DidGetKeysToClearPaymentInstruments,
+                     weak_ptr_factory_.GetWeakPtr(), registration->id(),
+                     std::move(callback)));
+}
+
+void PaymentAppDatabase::DidGetKeysToClearPaymentInstruments(
+    int64_t registration_id,
+    ClearPaymentInstrumentsCallback callback,
+    const std::vector<std::string>& keys,
+    PaymentHandlerStatus status) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+
+  if (status != PaymentHandlerStatus::SUCCESS) {
+    std::move(callback).Run(PaymentHandlerStatus::NOT_FOUND);
+    return;
+  }
+
+  std::vector<std::string> keys_with_prefix;
+  for (const auto& key : keys) {
+    keys_with_prefix.push_back(CreatePaymentInstrumentKey(key));
+    keys_with_prefix.push_back(CreatePaymentInstrumentKeyInfoKey(key));
+  }
+
+  service_worker_context_->ClearRegistrationUserData(
+      registration_id, keys_with_prefix,
+      base::Bind(&PaymentAppDatabase::DidClearPaymentInstruments,
+                 weak_ptr_factory_.GetWeakPtr(),
+                 base::Passed(std::move(callback))));
+}
+
+void PaymentAppDatabase::DidClearPaymentInstruments(
+    ClearPaymentInstrumentsCallback callback,
+    ServiceWorkerStatusCode status) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  return std::move(callback).Run(status == SERVICE_WORKER_OK
+                                     ? PaymentHandlerStatus::SUCCESS
+                                     : PaymentHandlerStatus::NOT_FOUND);
+}
+
 }  // namespace content
diff --git a/content/browser/payments/payment_app_database.h b/content/browser/payments/payment_app_database.h
index 9219e0b..9155f4f8 100644
--- a/content/browser/payments/payment_app_database.h
+++ b/content/browser/payments/payment_app_database.h
@@ -44,6 +44,8 @@
       base::OnceCallback<void(payments::mojom::PaymentHandlerStatus)>;
   using WritePaymentInstrumentCallback =
       base::OnceCallback<void(payments::mojom::PaymentHandlerStatus)>;
+  using ClearPaymentInstrumentsCallback =
+      base::OnceCallback<void(payments::mojom::PaymentHandlerStatus)>;
 
   explicit PaymentAppDatabase(
       scoped_refptr<ServiceWorkerContextWrapper> service_worker_context);
@@ -69,6 +71,8 @@
                               const std::string& instrument_key,
                               payments::mojom::PaymentInstrumentPtr instrument,
                               WritePaymentInstrumentCallback callback);
+  void ClearPaymentInstruments(const GURL& scope,
+                               ClearPaymentInstrumentsCallback callback);
 
  private:
   // WriteManifest callbacks
@@ -150,6 +154,20 @@
   void DidWritePaymentInstrument(WritePaymentInstrumentCallback callback,
                                  ServiceWorkerStatusCode status);
 
+  // ClearPaymentInstruments callbacks
+  void DidFindRegistrationToClearPaymentInstruments(
+      const GURL& scope,
+      ClearPaymentInstrumentsCallback callback,
+      ServiceWorkerStatusCode status,
+      scoped_refptr<ServiceWorkerRegistration> registration);
+  void DidGetKeysToClearPaymentInstruments(
+      int64_t registration_id,
+      ClearPaymentInstrumentsCallback callback,
+      const std::vector<std::string>& keys,
+      payments::mojom::PaymentHandlerStatus status);
+  void DidClearPaymentInstruments(ClearPaymentInstrumentsCallback callback,
+                                  ServiceWorkerStatusCode status);
+
   scoped_refptr<ServiceWorkerContextWrapper> service_worker_context_;
   base::WeakPtrFactory<PaymentAppDatabase> weak_ptr_factory_;
 
diff --git a/content/browser/payments/payment_manager.cc b/content/browser/payments/payment_manager.cc
index 0538e22..0503518f 100644
--- a/content/browser/payments/payment_manager.cc
+++ b/content/browser/payments/payment_manager.cc
@@ -102,6 +102,14 @@
       scope_, instrument_key, std::move(details), callback);
 }
 
+void PaymentManager::ClearPaymentInstruments(
+    const ClearPaymentInstrumentsCallback& callback) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+
+  payment_app_context_->payment_app_database()->ClearPaymentInstruments(
+      scope_, callback);
+}
+
 void PaymentManager::OnConnectionError() {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
   payment_app_context_->PaymentManagerHadConnectionError(this);
diff --git a/content/browser/payments/payment_manager.h b/content/browser/payments/payment_manager.h
index b0aa75b..0c23de9 100644
--- a/content/browser/payments/payment_manager.h
+++ b/content/browser/payments/payment_manager.h
@@ -51,6 +51,8 @@
       const std::string& instrument_key,
       payments::mojom::PaymentInstrumentPtr details,
       const SetPaymentInstrumentCallback& callback) override;
+  void ClearPaymentInstruments(
+      const ClearPaymentInstrumentsCallback& callback) override;
 
   // Called when an error is detected on binding_.
   void OnConnectionError();
diff --git a/content/browser/payments/payment_manager_unittest.cc b/content/browser/payments/payment_manager_unittest.cc
index 195d352..07e522c1 100644
--- a/content/browser/payments/payment_manager_unittest.cc
+++ b/content/browser/payments/payment_manager_unittest.cc
@@ -71,6 +71,11 @@
   *out_status = status;
 }
 
+void ClearPaymentInstrumentsCallback(PaymentHandlerStatus* out_status,
+                                     PaymentHandlerStatus status) {
+  *out_status = status;
+}
+
 }  // namespace
 
 class PaymentManagerTest : public PaymentAppContentUnitTestBase {
@@ -123,6 +128,12 @@
     base::RunLoop().RunUntilIdle();
   }
 
+  void ClearPaymentInstruments(PaymentHandlerStatus* out_status) {
+    manager_->ClearPaymentInstruments(
+        base::Bind(&ClearPaymentInstrumentsCallback, out_status));
+    base::RunLoop().RunUntilIdle();
+  }
+
  private:
   // Owned by payment_app_context_.
   PaymentManager* manager_;
@@ -294,4 +305,42 @@
   ASSERT_EQ("test_key2", keys[2]);
 }
 
+TEST_F(PaymentManagerTest, ClearPaymentInstruments) {
+  PaymentHandlerStatus status = PaymentHandlerStatus::NOT_FOUND;
+  std::vector<std::string> keys;
+  KeysOfPaymentInstruments(&keys, &status);
+  ASSERT_EQ(PaymentHandlerStatus::SUCCESS, status);
+  ASSERT_EQ(0U, keys.size());
+
+  {
+    PaymentHandlerStatus write_status = PaymentHandlerStatus::NOT_FOUND;
+    SetPaymentInstrument("test_key1", PaymentInstrument::New(), &write_status);
+    ASSERT_EQ(PaymentHandlerStatus::SUCCESS, write_status);
+  }
+  {
+    PaymentHandlerStatus write_status = PaymentHandlerStatus::NOT_FOUND;
+    SetPaymentInstrument("test_key3", PaymentInstrument::New(), &write_status);
+    ASSERT_EQ(PaymentHandlerStatus::SUCCESS, write_status);
+  }
+  {
+    PaymentHandlerStatus write_status = PaymentHandlerStatus::NOT_FOUND;
+    SetPaymentInstrument("test_key2", PaymentInstrument::New(), &write_status);
+    ASSERT_EQ(PaymentHandlerStatus::SUCCESS, write_status);
+  }
+
+  status = PaymentHandlerStatus::NOT_FOUND;
+  KeysOfPaymentInstruments(&keys, &status);
+  ASSERT_EQ(PaymentHandlerStatus::SUCCESS, status);
+  ASSERT_EQ(3U, keys.size());
+
+  status = PaymentHandlerStatus::NOT_FOUND;
+  ClearPaymentInstruments(&status);
+  ASSERT_EQ(PaymentHandlerStatus::SUCCESS, status);
+
+  status = PaymentHandlerStatus::NOT_FOUND;
+  KeysOfPaymentInstruments(&keys, &status);
+  ASSERT_EQ(PaymentHandlerStatus::SUCCESS, status);
+  ASSERT_EQ(0U, keys.size());
+}
+
 }  // namespace content
diff --git a/content/browser/renderer_host/compositor_impl_android.h b/content/browser/renderer_host/compositor_impl_android.h
index 6acdf23..60867cb 100644
--- a/content/browser/renderer_host/compositor_impl_android.h
+++ b/content/browser/renderer_host/compositor_impl_android.h
@@ -85,6 +85,7 @@
   void DidBeginMainFrame() override {}
   void BeginMainFrame(const cc::BeginFrameArgs& args) override {}
   void BeginMainFrameNotExpectedSoon() override {}
+  void BeginMainFrameNotExpectedUntil(base::TimeTicks time) override {}
   void UpdateLayerTreeHost() override;
   void ApplyViewportDeltas(const gfx::Vector2dF& inner_delta,
                            const gfx::Vector2dF& outer_delta,
diff --git a/content/renderer/gpu/render_widget_compositor.cc b/content/renderer/gpu/render_widget_compositor.cc
index 9733a8c..4e93f94 100644
--- a/content/renderer/gpu/render_widget_compositor.cc
+++ b/content/renderer/gpu/render_widget_compositor.cc
@@ -1088,6 +1088,12 @@
   compositor_deps_->GetRendererScheduler()->BeginFrameNotExpectedSoon();
 }
 
+void RenderWidgetCompositor::BeginMainFrameNotExpectedUntil(
+    base::TimeTicks time) {
+  compositor_deps_->GetRendererScheduler()->BeginMainFrameNotExpectedUntil(
+      time);
+}
+
 void RenderWidgetCompositor::UpdateLayerTreeHost() {
   delegate_->UpdateVisualState();
 }
diff --git a/content/renderer/gpu/render_widget_compositor.h b/content/renderer/gpu/render_widget_compositor.h
index a2692a09..1da6b8b 100644
--- a/content/renderer/gpu/render_widget_compositor.h
+++ b/content/renderer/gpu/render_widget_compositor.h
@@ -185,6 +185,7 @@
   void DidBeginMainFrame() override;
   void BeginMainFrame(const cc::BeginFrameArgs& args) override;
   void BeginMainFrameNotExpectedSoon() override;
+  void BeginMainFrameNotExpectedUntil(base::TimeTicks time) override;
   void UpdateLayerTreeHost() override;
   void ApplyViewportDeltas(const gfx::Vector2dF& inner_delta,
                            const gfx::Vector2dF& outer_delta,
diff --git a/gpu/ipc/service/gpu_init.cc b/gpu/ipc/service/gpu_init.cc
index 4588f9aa..020f414 100644
--- a/gpu/ipc/service/gpu_init.cc
+++ b/gpu/ipc/service/gpu_init.cc
@@ -163,8 +163,22 @@
 
   // Start the GPU watchdog only after anything that is expected to be time
   // consuming has completed, otherwise the process is liable to be aborted.
-  if (enable_watchdog && !delayed_watchdog_enable)
+  if (enable_watchdog && !delayed_watchdog_enable) {
     watchdog_thread_ = gpu::GpuWatchdogThread::Create();
+#if defined(OS_WIN)
+    // This is a workaround for an occasional deadlock between watchdog and
+    // current thread. Watchdog hangs at thread initialization in
+    // __acrt_thread_attach() and current thread in std::setlocale(...)
+    // (during InitializeGLOneOff()). Source of the deadlock looks like an old
+    // UCRT bug that was supposed to be fixed in 10.0.10586 release of UCRT,
+    // but we might have come accross a not-yet-covered scenario.
+    // References:
+    // https://bugs.python.org/issue26624
+    // http://stackoverflow.com/questions/35572792/setlocale-stuck-on-windows
+    auto watchdog_started = watchdog_thread_->WaitUntilThreadStarted();
+    DCHECK(watchdog_started);
+#endif  // OS_WIN
+  }
 
   // Get vendor_id, device_id, driver_version from browser process through
   // commandline switches.
diff --git a/media/audio/audio_input_device.cc b/media/audio/audio_input_device.cc
index 07b966c..3b83deb1 100644
--- a/media/audio/audio_input_device.cc
+++ b/media/audio/audio_input_device.cc
@@ -9,7 +9,6 @@
 
 #include "base/bind.h"
 #include "base/macros.h"
-#include "base/memory/scoped_vector.h"
 #include "base/strings/stringprintf.h"
 #include "base/threading/thread_restrictions.h"
 #include "base/time/time.h"
@@ -47,7 +46,7 @@
   const double bytes_per_ms_;
   int current_segment_id_;
   uint32_t last_buffer_id_;
-  ScopedVector<media::AudioBus> audio_buses_;
+  std::vector<std::unique_ptr<media::AudioBus>> audio_buses_;
   CaptureCallback* capture_callback_;
 
   DISALLOW_COPY_AND_ASSIGN(AudioThreadCallback);
@@ -297,9 +296,8 @@
   for (int i = 0; i < total_segments_; ++i) {
     media::AudioInputBuffer* buffer =
         reinterpret_cast<media::AudioInputBuffer*>(ptr);
-    std::unique_ptr<media::AudioBus> audio_bus =
-        media::AudioBus::WrapMemory(audio_parameters_, buffer->audio);
-    audio_buses_.push_back(std::move(audio_bus));
+    audio_buses_.push_back(
+        media::AudioBus::WrapMemory(audio_parameters_, buffer->audio));
     ptr += segment_length_;
   }
 
@@ -341,7 +339,7 @@
   last_buffer_id_ = buffer->params.id;
 
   // Use pre-allocated audio bus wrapping existing block of shared memory.
-  media::AudioBus* audio_bus = audio_buses_[current_segment_id_];
+  media::AudioBus* audio_bus = audio_buses_[current_segment_id_].get();
 
   // Deliver captured data to the client in floating point format and update
   // the audio delay measurement.
diff --git a/media/audio/audio_manager_base.cc b/media/audio/audio_manager_base.cc
index 498aa1f..e149216 100644
--- a/media/audio/audio_manager_base.cc
+++ b/media/audio/audio_manager_base.cc
@@ -68,7 +68,8 @@
  public:
   explicit CompareByParams(const DispatcherParams* dispatcher)
       : dispatcher_(dispatcher) {}
-  bool operator()(DispatcherParams* dispatcher_in) const {
+  bool operator()(
+      const std::unique_ptr<DispatcherParams>& dispatcher_in) const {
     // We will reuse the existing dispatcher when:
     // 1) Unified IO is not used, input_params and output_params of the
     //    existing dispatcher are the same as the requested dispatcher.
@@ -277,16 +278,14 @@
     }
   }
 
-  DispatcherParams* dispatcher_params =
-      new DispatcherParams(params, output_params, output_device_id);
+  std::unique_ptr<DispatcherParams> dispatcher_params =
+      base::MakeUnique<DispatcherParams>(params, output_params,
+                                         output_device_id);
 
-  AudioOutputDispatchers::iterator it =
-      std::find_if(output_dispatchers_.begin(), output_dispatchers_.end(),
-                   CompareByParams(dispatcher_params));
-  if (it != output_dispatchers_.end()) {
-    delete dispatcher_params;
+  auto it = std::find_if(output_dispatchers_.begin(), output_dispatchers_.end(),
+                         CompareByParams(dispatcher_params.get()));
+  if (it != output_dispatchers_.end())
     return (*it)->dispatcher->CreateStreamProxy();
-  }
 
   const base::TimeDelta kCloseDelay =
       base::TimeDelta::FromSeconds(kStreamCloseDelaySeconds);
@@ -308,8 +307,8 @@
   }
 
   dispatcher_params->dispatcher = std::move(dispatcher);
-  output_dispatchers_.push_back(dispatcher_params);
-  return dispatcher_params->dispatcher->CreateStreamProxy();
+  output_dispatchers_.push_back(std::move(dispatcher_params));
+  return output_dispatchers_.back()->dispatcher->CreateStreamProxy();
 }
 
 void AudioManagerBase::ShowAudioInputSettings() {
diff --git a/media/audio/audio_manager_base.h b/media/audio/audio_manager_base.h
index 91a9c8c..38748c5 100644
--- a/media/audio/audio_manager_base.h
+++ b/media/audio/audio_manager_base.h
@@ -12,7 +12,6 @@
 
 #include "base/compiler_specific.h"
 #include "base/macros.h"
-#include "base/memory/scoped_vector.h"
 #include "base/observer_list.h"
 #include "base/threading/thread.h"
 #include "build/build_config.h"
@@ -166,7 +165,7 @@
   FRIEND_TEST_ALL_PREFIXES(AudioManagerTest, AudioDebugRecording);
 
   struct DispatcherParams;
-  typedef ScopedVector<DispatcherParams> AudioOutputDispatchers;
+  typedef std::vector<std::unique_ptr<DispatcherParams>> AudioOutputDispatchers;
 
   class CompareByParams;
 
diff --git a/net/BUILD.gn b/net/BUILD.gn
index 0ba0ee7..e95ef6d 100644
--- a/net/BUILD.gn
+++ b/net/BUILD.gn
@@ -172,7 +172,6 @@
     "cert/crl_set.h",
     "cert/ct_known_logs.cc",
     "cert/ct_known_logs.h",
-    "cert/ct_known_logs_static-inc.h",
     "cert/ct_policy_enforcer.cc",
     "cert/ct_policy_enforcer.h",
     "cert/ct_policy_status.h",
@@ -360,6 +359,7 @@
     ":net_resources",
     "//base",
     "//net/base/registry_controlled_domains",
+    "//net/data/ssl/certificate_transparency:ct_log_list",
     "//net/data/ssl/wosign:wosign_domains",
     "//third_party/protobuf:protobuf_lite",
     "//url:url_features",
@@ -4905,6 +4905,7 @@
     "//crypto:platform",
     "//crypto:test_support",
     "//net/base/registry_controlled_domains",
+    "//net/data/ssl/certificate_transparency:ct_log_list",
     "//net/http:transport_security_state_unittest_data",
     "//testing/gmock",
     "//testing/gtest",
diff --git a/net/cert/ct_known_logs.cc b/net/cert/ct_known_logs.cc
index cca1fbe..a1e80c7 100644
--- a/net/cert/ct_known_logs.cc
+++ b/net/cert/ct_known_logs.cc
@@ -25,7 +25,7 @@
 
 namespace {
 
-#include "net/cert/ct_known_logs_static-inc.h"
+#include "net/data/ssl/certificate_transparency/log_list-inc.cc"
 
 }  // namespace
 
diff --git a/net/cert/ct_known_logs_static-inc.h b/net/cert/ct_known_logs_static-inc.h
deleted file mode 100644
index ac86f5a..0000000
--- a/net/cert/ct_known_logs_static-inc.h
+++ /dev/null
@@ -1,231 +0,0 @@
-// Copyright 2016 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.
-
-struct CTLogInfo {
-  // The DER-encoded SubjectPublicKeyInfo for the log.
-  const char* log_key;
-  // The length, in bytes, of |log_key|.
-  size_t log_key_length;
-  // The user-friendly log name.
-  // Note: This will not be translated.
-  const char* log_name;
-  // The HTTPS API endpoint for the log.
-  // Note: Trailing slashes should be included.
-  const char* log_url;
-  // The DNS API endpoint for the log.
-  // This is used as the parent domain for all queries about the log.
-  // If empty, CT DNS queries are not supported for the log. This will prevent
-  // retrieval of inclusion proofs over DNS for SCTs from the log.
-  // https://github.com/google/certificate-transparency-rfcs/blob/master/dns/draft-ct-over-dns.md.
-  const char* log_dns_domain;
-};
-
-// The set of all presently-qualifying CT logs.
-// Google provides DNS frontends for all of the logs.
-const CTLogInfo kCTLogList[] = {
-    {"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86"
-     "\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x7d\xa8\x4b\x12\x29\x80\xa3"
-     "\x3d\xad\xd3\x5a\x77\xb8\xcc\xe2\x88\xb3\xa5\xfd\xf1\xd3\x0c\xcd\x18"
-     "\x0c\xe8\x41\x46\xe8\x81\x01\x1b\x15\xe1\x4b\xf1\x1b\x62\xdd\x36\x0a"
-     "\x08\x18\xba\xed\x0b\x35\x84\xd0\x9e\x40\x3c\x2d\x9e\x9b\x82\x65\xbd"
-     "\x1f\x04\x10\x41\x4c\xa0",
-     91, "Google 'Pilot' log", "https://ct.googleapis.com/pilot/",
-     "pilot.ct.googleapis.com"},
-    {"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86"
-     "\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\xd7\xf4\xcc\x69\xb2\xe4\x0e"
-     "\x90\xa3\x8a\xea\x5a\x70\x09\x4f\xef\x13\x62\xd0\x8d\x49\x60\xff\x1b"
-     "\x40\x50\x07\x0c\x6d\x71\x86\xda\x25\x49\x8d\x65\xe1\x08\x0d\x47\x34"
-     "\x6b\xbd\x27\xbc\x96\x21\x3e\x34\xf5\x87\x76\x31\xb1\x7f\x1d\xc9\x85"
-     "\x3b\x0d\xf7\x1f\x3f\xe9",
-     91, "Google 'Aviator' log", "https://ct.googleapis.com/aviator/",
-     "aviator.ct.googleapis.com"},
-    {"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86"
-     "\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x02\x46\xc5\xbe\x1b\xbb\x82"
-     "\x40\x16\xe8\xc1\xd2\xac\x19\x69\x13\x59\xf8\xf8\x70\x85\x46\x40\xb9"
-     "\x38\xb0\x23\x82\xa8\x64\x4c\x7f\xbf\xbb\x34\x9f\x4a\x5f\x28\x8a\xcf"
-     "\x19\xc4\x00\xf6\x36\x06\x93\x65\xed\x4c\xf5\xa9\x21\x62\x5a\xd8\x91"
-     "\xeb\x38\x24\x40\xac\xe8",
-     91, "DigiCert Log Server", "https://ct1.digicert-ct.com/log/",
-     "digicert.ct.googleapis.com"},
-    {"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86"
-     "\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x20\x5b\x18\xc8\x3c\xc1\x8b"
-     "\xb3\x31\x08\x00\xbf\xa0\x90\x57\x2b\xb7\x47\x8c\x6f\xb5\x68\xb0\x8e"
-     "\x90\x78\xe9\xa0\x73\xea\x4f\x28\x21\x2e\x9c\xc0\xf4\x16\x1b\xaa\xf9"
-     "\xd5\xd7\xa9\x80\xc3\x4e\x2f\x52\x3c\x98\x01\x25\x46\x24\x25\x28\x23"
-     "\x77\x2d\x05\xc2\x40\x7a",
-     91, "Google 'Rocketeer' log", "https://ct.googleapis.com/rocketeer/",
-     "rocketeer.ct.googleapis.com"},
-    {"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86"
-     "\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x96\xea\xac\x1c\x46\x0c\x1b"
-     "\x55\xdc\x0d\xfc\xb5\x94\x27\x46\x57\x42\x70\x3a\x69\x18\xe2\xbf\x3b"
-     "\xc4\xdb\xab\xa0\xf4\xb6\x6c\xc0\x53\x3f\x4d\x42\x10\x33\xf0\x58\x97"
-     "\x8f\x6b\xbe\x72\xf4\x2a\xec\x1c\x42\xaa\x03\x2f\x1a\x7e\x28\x35\x76"
-     "\x99\x08\x3d\x21\x14\x86",
-     91, "Symantec log", "https://ct.ws.symantec.com/",
-     "symantec.ct.googleapis.com"},
-    {"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86"
-     "\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\xea\x95\x9e\x02\xff\xee\xf1"
-     "\x33\x6d\x4b\x87\xbc\xcd\xfd\x19\x17\x62\xff\x94\xd3\xd0\x59\x07\x3f"
-     "\x02\x2d\x1c\x90\xfe\xc8\x47\x30\x3b\xf1\xdd\x0d\xb8\x11\x0c\x5d\x1d"
-     "\x86\xdd\xab\xd3\x2b\x46\x66\xfb\x6e\x65\xb7\x3b\xfd\x59\x68\xac\xdf"
-     "\xa6\xf8\xce\xd2\x18\x4d",
-     91, "Symantec 'Vega' log", "https://vega.ws.symantec.com/",
-     "symantec-vega.ct.googleapis.com"},
-    {"\x30\x82\x01\x22\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01"
-     "\x05\x00\x03\x82\x01\x0f\x00\x30\x82\x01\x0a\x02\x82\x01\x01\x00\xbf"
-     "\xb5\x08\x61\x9a\x29\x32\x04\xd3\x25\x63\xe9\xd8\x85\xe1\x86\xe0\x1f"
-     "\xd6\x5e\x9a\xf7\x33\x3b\x80\x1b\xe7\xb6\x3e\x5f\x2d\xa1\x66\xf6\x95"
-     "\x4a\x84\xa6\x21\x56\x79\xe8\xf7\x85\xee\x5d\xe3\x7c\x12\xc0\xe0\x89"
-     "\x22\x09\x22\x3e\xba\x16\x95\x06\xbd\xa8\xb9\xb1\xa9\xb2\x7a\xd6\x61"
-     "\x2e\x87\x11\xb9\x78\x40\x89\x75\xdb\x0c\xdc\x90\xe0\xa4\x79\xd6\xd5"
-     "\x5e\x6e\xd1\x2a\xdb\x34\xf4\x99\x3f\x65\x89\x3b\x46\xc2\x29\x2c\x15"
-     "\x07\x1c\xc9\x4b\x1a\x54\xf8\x6c\x1e\xaf\x60\x27\x62\x0a\x65\xd5\x9a"
-     "\xb9\x50\x36\x16\x6e\x71\xf6\x1f\x01\xf7\x12\xa7\xfc\xbf\xf6\x21\xa3"
-     "\x29\x90\x86\x2d\x77\xde\xbb\x4c\xd4\xcf\xfd\xd2\xcf\x82\x2c\x4d\xd4"
-     "\xf2\xc2\x2d\xac\xa9\xbe\xea\xc3\x19\x25\x43\xb2\xe5\x9a\x6c\x0d\xc5"
-     "\x1c\xa5\x8b\xf7\x3f\x30\xaf\xb9\x01\x91\xb7\x69\x12\x12\xe5\x83\x61"
-     "\xfe\x34\x00\xbe\xf6\x71\x8a\xc7\xeb\x50\x92\xe8\x59\xfe\x15\x91\xeb"
-     "\x96\x97\xf8\x23\x54\x3f\x2d\x8e\x07\xdf\xee\xda\xb3\x4f\xc8\x3c\x9d"
-     "\x6f\xdf\x3c\x2c\x43\x57\xa1\x47\x0c\x91\x04\xf4\x75\x4d\xda\x89\x81"
-     "\xa4\x14\x06\x34\xb9\x98\xc3\xda\xf1\xfd\xed\x33\x36\xd3\x16\x2d\x35"
-     "\x02\x03\x01\x00\x01",
-     294, "CNNIC CT log", "https://ctserver.cnnic.cn/",
-     "cnnic.ct.googleapis.com"},
-    {"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86"
-     "\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\xcc\x11\x88\x7b\x2d\x66\xcb"
-     "\xae\x8f\x4d\x30\x66\x27\x19\x25\x22\x93\x21\x46\xb4\x2f\x01\xd3\xc6"
-     "\xf9\x2b\xd5\xc8\xba\x73\x9b\x06\xa2\xf0\x8a\x02\x9c\xd0\x6b\x46\x18"
-     "\x30\x85\xba\xe9\x24\x8b\x0e\xd1\x5b\x70\x28\x0c\x7e\xf1\x3a\x45\x7f"
-     "\x5a\xf3\x82\x42\x60\x31",
-     91, "WoSign log", "https://ctlog.wosign.com/",
-     "wosign1.ct.googleapis.com"},
-    {"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86"
-     "\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x48\xf3\x59\xf3\xf6\x05\x18"
-     "\xd3\xdb\xb2\xed\x46\x7e\xcf\xc8\x11\xb5\x57\xb1\xa8\xd6\x4c\xe6\x9f"
-     "\xb7\x4a\x1a\x14\x86\x43\xa9\x48\xb0\xcb\x5a\x3f\x3c\x4a\xca\xdf\xc4"
-     "\x82\x14\x55\x9a\xf8\xf7\x8e\x40\x55\xdc\xf4\xd2\xaf\xea\x75\x74\xfb"
-     "\x4e\x7f\x60\x86\x2e\x51",
-     91, "StartCom CT log", "https://ct.startssl.com/",
-     "startcom1.ct.googleapis.com"},
-    {"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86"
-     "\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x12\x6c\x86\x0e\xf6\x17\xb1"
-     "\x12\x6c\x37\x25\xd2\xad\x87\x3d\x0e\x31\xec\x21\xad\xb1\xcd\xbe\x14"
-     "\x47\xb6\x71\x56\x85\x7a\x9a\xb7\x3d\x89\x90\x7b\xc6\x32\x3a\xf8\xda"
-     "\xce\x8b\x01\xfe\x3f\xfc\x71\x91\x19\x8e\x14\x6e\x89\x7a\x5d\xb4\xab"
-     "\x7e\xe1\x4e\x1e\x7c\xac",
-     91, "Google 'Skydiver' log", "https://ct.googleapis.com/skydiver/",
-     "skydiver.ct.googleapis.com"},
-    {"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86"
-     "\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x4e\xd2\xbc\xbf\xb3\x08\x0a"
-     "\xf7\xb9\xea\xa4\xc7\x1c\x38\x61\x04\xeb\x95\xe0\x89\x54\x68\x44\xb1"
-     "\x66\xbc\x82\x7e\x4f\x50\x6c\x6f\x5c\xa3\xf0\xaa\x3e\xf4\xec\x80\xf0"
-     "\xdb\x0a\x9a\x7a\xa0\x5b\x72\x00\x7c\x25\x0e\x19\xef\xaf\xb2\x62\x8d"
-     "\x74\x43\xf4\x26\xf6\x14",
-     91, "Google 'Icarus' log", "https://ct.googleapis.com/icarus/",
-     "icarus.ct.googleapis.com"},
-    {"\x30\x82\x01\x22\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01"
-     "\x05\x00\x03\x82\x01\x0f\x00\x30\x82\x01\x0a\x02\x82\x01\x01\x00\xac"
-     "\xcf\x2f\x4b\x70\xac\xf1\x0d\x96\xbf\xe8\x0a\xfe\x44\x9d\xd4\x8c\x17"
-     "\x9d\xc3\x9a\x10\x11\x84\x13\xed\x8c\xf9\x37\x6d\x83\xe4\x00\x6f\xb1"
-     "\x4b\xc0\xa6\x89\xc7\x61\x8f\x9a\x34\xbb\x56\x52\xca\x03\x56\x50\xef"
-     "\x24\x7f\x4b\x49\xe9\x35\x81\xdd\xf0\xe7\x17\xf5\x72\xd2\x23\xc5\xe3"
-     "\x13\x7f\xd7\x8e\x78\x35\x8f\x49\xde\x98\x04\x8a\x63\xaf\xad\xa2\x39"
-     "\x70\x95\x84\x68\x4b\x91\x33\xfe\x4c\xe1\x32\x17\xc2\xf2\x61\xb8\x3a"
-     "\x8d\x39\x7f\xd5\x95\x82\x3e\x56\x19\x50\x45\x6f\xcb\x08\x33\x0d\xd5"
-     "\x19\x42\x08\x1a\x48\x42\x10\xf1\x68\xc3\xc3\x41\x13\xcb\x0d\x1e\xdb"
-     "\x02\xb7\x24\x7a\x51\x96\x6e\xbc\x08\xea\x69\xaf\x6d\xef\x92\x98\x8e"
-     "\x55\xf3\x65\xe5\xe8\x9c\xbe\x1a\x47\x60\x30\x7d\x7a\x80\xad\x56\x83"
-     "\x7a\x93\xc3\xae\x93\x2b\x6a\x28\x8a\xa6\x5f\x63\x19\x0c\xbe\x7c\x7b"
-     "\x21\x63\x41\x38\xb7\xf7\xe8\x76\x73\x6b\x85\xcc\xbc\x72\x2b\xc1\x52"
-     "\xd0\x5b\x5d\x31\x4e\x9d\x2a\xf3\x4d\x9b\x64\x14\x99\x26\xc6\x71\xf8"
-     "\x7b\xf8\x44\xd5\xe3\x23\x20\xf3\x0a\xd7\x8b\x51\x3e\x72\x80\xd2\x78"
-     "\x78\x35\x2d\x4a\xe7\x40\x99\x11\x95\x34\xd4\x2f\x7f\xf9\x5f\x35\x37"
-     "\x02\x03\x01\x00\x01",
-     294, "PuChuangSiDa CT Log 1", "https://www.certificatetransparency.cn/ct/",
-     "puchuangsida1.ct.googleapis.com"}};
-
-// Information related to previously-qualified, but now disqualified, CT
-// logs.
-struct DisqualifiedCTLogInfo {
-  // The ID of the log (the SHA-256 hash of |log_info.log_key|.
-  const char log_id[33];
-
-  const CTLogInfo log_info;
-
-  // The offset from the Unix Epoch of when the log was disqualified.
-  // SCTs embedded in pre-certificates after this date should not count
-  // towards any uniqueness/freshness requirements.
-  const base::TimeDelta disqualification_date;
-};
-
-// The set of all disqualified logs, sorted by |log_id|.
-const DisqualifiedCTLogInfo kDisqualifiedCTLogList[] = {
-    {
-        "\x74\x61\xb4\xa0\x9c\xfb\x3d\x41\xd7\x51\x59\x57\x5b\x2e\x76\x49\xa4"
-        "\x45\xa8\xd2\x77\x09\xb0\xcc\x56\x4a\x64\x82\xb7\xeb\x41\xa3",
-        {"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86"
-         "\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x27\x64\x39\x0c\x2d\xdc\x50"
-         "\x18\xf8\x21\x00\xa2\x0e\xed\x2c\xea\x3e\x75\xba\x9f\x93\x64\x09\x00"
-         "\x11\xc4\x11\x17\xab\x5c\xcf\x0f\x74\xac\xb5\x97\x90\x93\x00\x5b\xb8"
-         "\xeb\xf7\x27\x3d\xd9\xb2\x0a\x81\x5f\x2f\x0d\x75\x38\x94\x37\x99\x1e"
-         "\xf6\x07\x76\xe0\xee\xbe",
-         91, "Izenpe log", "https://ct.izenpe.com/",
-         "izenpe1.ct.googleapis.com"},
-        // 2016-05-30 00:00:00 UTC
-        base::TimeDelta::FromSeconds(1464566400),
-    },
-    {
-        "\xac\x3b\x9a\xed\x7f\xa9\x67\x47\x57\x15\x9e\x6d\x7d\x57\x56\x72\xf9"
-        "\xd9\x81\x00\x94\x1e\x9b\xde\xff\xec\xa1\x31\x3b\x75\x78\x2d",
-        {"\x30\x82\x01\x22\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01"
-         "\x05\x00\x03\x82\x01\x0f\x00\x30\x82\x01\x0a\x02\x82\x01\x01\x00\xa2"
-         "\x5a\x48\x1f\x17\x52\x95\x35\xcb\xa3\x5b\x3a\x1f\x53\x82\x76\x94\xa3"
-         "\xff\x80\xf2\x1c\x37\x3c\xc0\xb1\xbd\xc1\x59\x8b\xab\x2d\x65\x93\xd7"
-         "\xf3\xe0\x04\xd5\x9a\x6f\xbf\xd6\x23\x76\x36\x4f\x23\x99\xcb\x54\x28"
-         "\xad\x8c\x15\x4b\x65\x59\x76\x41\x4a\x9c\xa6\xf7\xb3\x3b\x7e\xb1\xa5"
-         "\x49\xa4\x17\x51\x6c\x80\xdc\x2a\x90\x50\x4b\x88\x24\xe9\xa5\x12\x32"
-         "\x93\x04\x48\x90\x02\xfa\x5f\x0e\x30\x87\x8e\x55\x76\x05\xee\x2a\x4c"
-         "\xce\xa3\x6a\x69\x09\x6e\x25\xad\x82\x76\x0f\x84\x92\xfa\x38\xd6\x86"
-         "\x4e\x24\x8f\x9b\xb0\x72\xcb\x9e\xe2\x6b\x3f\xe1\x6d\xc9\x25\x75\x23"
-         "\x88\xa1\x18\x58\x06\x23\x33\x78\xda\x00\xd0\x38\x91\x67\xd2\xa6\x7d"
-         "\x27\x97\x67\x5a\xc1\xf3\x2f\x17\xe6\xea\xd2\x5b\xe8\x81\xcd\xfd\x92"
-         "\x68\xe7\xf3\x06\xf0\xe9\x72\x84\xee\x01\xa5\xb1\xd8\x33\xda\xce\x83"
-         "\xa5\xdb\xc7\xcf\xd6\x16\x7e\x90\x75\x18\xbf\x16\xdc\x32\x3b\x6d\x8d"
-         "\xab\x82\x17\x1f\x89\x20\x8d\x1d\x9a\xe6\x4d\x23\x08\xdf\x78\x6f\xc6"
-         "\x05\xbf\x5f\xae\x94\x97\xdb\x5f\x64\xd4\xee\x16\x8b\xa3\x84\x6c\x71"
-         "\x2b\xf1\xab\x7f\x5d\x0d\x32\xee\x04\xe2\x90\xec\x41\x9f\xfb\x39\xc1"
-         "\x02\x03\x01\x00\x01",
-         294, "Venafi log", "https://ctlog.api.venafi.com/",
-         "venafi.ct.googleapis.com"},
-        // 2017-02-28 18:42:26 UTC
-        base::TimeDelta::FromSeconds(1488307346),
-    },
-    {
-        "\xcd\xb5\x17\x9b\x7f\xc1\xc0\x46\xfe\xea\x31\x13\x6a\x3f\x8f\x00\x2e"
-        "\x61\x82\xfa\xf8\x89\x6f\xec\xc8\xb2\xf5\xb5\xab\x60\x49\x00",
-        {"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86"
-         "\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x0b\x23\xcb\x85\x62\x98\x61"
-         "\x48\x04\x73\xeb\x54\x5d\xf3\xd0\x07\x8c\x2d\x19\x2d\x8c\x36\xf5\xeb"
-         "\x8f\x01\x42\x0a\x7c\x98\x26\x27\xc1\xb5\xdd\x92\x93\xb0\xae\xf8\x9b"
-         "\x3d\x0c\xd8\x4c\x4e\x1d\xf9\x15\xfb\x47\x68\x7b\xba\x66\xb7\x25\x9c"
-         "\xd0\x4a\xc2\x66\xdb\x48",
-         91, "Certly.IO log", "https://log.certly.io/",
-         "certly.ct.googleapis.com"},
-        // 2016-04-15 00:00:00 UTC
-        base::TimeDelta::FromSeconds(1460678400),
-    },
-};
-
-// The list is sorted.
-const char kGoogleLogIDs[][33] = {
-    "\x29\x3c\x51\x96\x54\xc8\x39\x65\xba\xaa\x50\xfc\x58\x07\xd4\xb7\x6f"
-    "\xbf\x58\x7a\x29\x72\xdc\xa4\xc3\x0c\xf4\xe5\x45\x47\xf4\x78",
-    "\x68\xf6\x98\xf8\x1f\x64\x82\xbe\x3a\x8c\xee\xb9\x28\x1d\x4c\xfc\x71"
-    "\x51\x5d\x67\x93\xd4\x44\xd1\x0a\x67\xac\xbb\x4f\x4f\xfb\xc4",
-    "\xa4\xb9\x09\x90\xb4\x18\x58\x14\x87\xbb\x13\xa2\xcc\x67\x70\x0a\x3c"
-    "\x35\x98\x04\xf9\x1b\xdf\xb8\xe3\x77\xcd\x0e\xc8\x0d\xdc\x10",
-    "\xbb\xd9\xdf\xbc\x1f\x8a\x71\xb5\x93\x94\x23\x97\xaa\x92\x7b\x47\x38"
-    "\x57\x95\x0a\xab\x52\xe8\x1a\x90\x96\x64\x36\x8e\x1e\xd1\x85",
-    "\xee\x4b\xbd\xb7\x75\xce\x60\xba\xe1\x42\x69\x1f\xab\xe1\x9e\x66\xa3"
-    "\x0f\x7e\x5f\xb0\x72\xd8\x83\x00\xc4\x7b\x89\x7a\xa8\xfd\xcb"};
diff --git a/net/cert/ct_known_logs_unittest.cc b/net/cert/ct_known_logs_unittest.cc
index f5e937a..59a074e7 100644
--- a/net/cert/ct_known_logs_unittest.cc
+++ b/net/cert/ct_known_logs_unittest.cc
@@ -15,9 +15,7 @@
 namespace net {
 
 namespace {
-
-#include "net/cert/ct_known_logs_static-inc.h"
-
+#include "net/data/ssl/certificate_transparency/log_list-inc.cc"
 }  // namespace
 
 TEST(CTKnownLogsTest, GoogleIDsAreSorted) {
diff --git a/net/data/ssl/certificate_transparency/BUILD.gn b/net/data/ssl/certificate_transparency/BUILD.gn
new file mode 100644
index 0000000..8c5eb6c3
--- /dev/null
+++ b/net/data/ssl/certificate_transparency/BUILD.gn
@@ -0,0 +1,18 @@
+# Copyright (c) 2017 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.
+
+action_foreach("ct_log_list") {
+  script = "//net/tools/ct_log_list/make_ct_known_logs_list.py"
+  sources = [
+    "log_list.json",
+  ]
+  outputs = [
+    "${target_gen_dir}/{{source_name_part}}-inc.cc",
+  ]
+  args = [
+    "{{source}}",
+    rebase_path("${target_gen_dir}/{{source_name_part}}-inc.cc",
+                root_build_dir),
+  ]
+}
diff --git a/net/data/ssl/certificate_transparency/log_list.json b/net/data/ssl/certificate_transparency/log_list.json
new file mode 100644
index 0000000..97d6df3c0
--- /dev/null
+++ b/net/data/ssl/certificate_transparency/log_list.json
@@ -0,0 +1,205 @@
+{
+ "logs": [
+  {
+   "description": "Google 'Aviator' log",
+   "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1/TMabLkDpCjiupacAlP7xNi0I1JYP8bQFAHDG1xhtolSY1l4QgNRzRrvSe8liE+NPWHdjGxfx3JhTsN9x8/6Q==",
+   "url": "ct.googleapis.com/aviator/",
+   "maximum_merge_delay": 86400,
+   "operated_by": [
+    0
+   ],
+   "final_sth": {
+    "tree_size": 46466472,
+    "timestamp": 1480512258330,
+    "sha256_root_hash": "LcGcZRsm+LGYmrlyC5LXhV1T6OD8iH5dNlb0sEJl9bA=",
+    "tree_head_signature": "BAMASDBGAiEA/M0Nvt77aNe+9eYbKsv6rRpTzFTKa5CGqb56ea4hnt8CIQCJDE7pL6xgAewMd5i3G1lrBWgFooT2kd3+zliEz5Rw8w=="
+   },
+   "dns_api_endpoint": "aviator.ct.googleapis.com"
+  },
+  {
+   "description": "Google 'Icarus' log",
+   "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAETtK8v7MICve56qTHHDhhBOuV4IlUaESxZryCfk9QbG9co/CqPvTsgPDbCpp6oFtyAHwlDhnvr7JijXRD9Cb2FA==",
+   "url": "ct.googleapis.com/icarus/",
+   "maximum_merge_delay": 86400,
+   "operated_by": [
+    0
+   ],
+   "dns_api_endpoint": "icarus.ct.googleapis.com"
+  },
+  {
+   "description": "Google 'Pilot' log",
+   "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfahLEimAoz2t01p3uMziiLOl/fHTDM0YDOhBRuiBARsV4UvxG2LdNgoIGLrtCzWE0J5APC2em4JlvR8EEEFMoA==",
+   "url": "ct.googleapis.com/pilot/",
+   "maximum_merge_delay": 86400,
+   "operated_by": [
+    0
+   ],
+   "dns_api_endpoint": "pilot.ct.googleapis.com"
+  },
+  {
+   "description": "Google 'Rocketeer' log",
+   "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIFsYyDzBi7MxCAC/oJBXK7dHjG+1aLCOkHjpoHPqTyghLpzA9BYbqvnV16mAw04vUjyYASVGJCUoI3ctBcJAeg==",
+   "url": "ct.googleapis.com/rocketeer/",
+   "maximum_merge_delay": 86400,
+   "operated_by": [
+    0
+   ],
+   "dns_api_endpoint": "rocketeer.ct.googleapis.com"
+  },
+  {
+   "description": "Google 'Skydiver' log",
+   "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEmyGDvYXsRJsNyXSrYc9DjHsIa2xzb4UR7ZxVoV6mrc9iZB7xjI6+NrOiwH+P/xxkRmOFG6Jel20q37hTh58rA==",
+   "url": "ct.googleapis.com/skydiver/",
+   "maximum_merge_delay": 86400,
+   "operated_by": [
+    0
+   ],
+   "dns_api_endpoint": "skydiver.ct.googleapis.com"
+  },
+  {
+   "description": "DigiCert Log Server",
+   "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAkbFvhu7gkAW6MHSrBlpE1n4+HCFRkC5OLAjgqhkTH+/uzSfSl8ois8ZxAD2NgaTZe1M9akhYlrYkes4JECs6A==",
+   "url": "ct1.digicert-ct.com/log/",
+   "maximum_merge_delay": 86400,
+   "operated_by": [
+    1
+   ],
+   "dns_api_endpoint": "digicert.ct.googleapis.com"
+  },
+  {
+   "description": "Symantec log",
+   "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEluqsHEYMG1XcDfy1lCdGV0JwOmkY4r87xNuroPS2bMBTP01CEDPwWJePa75y9CrsHEKqAy8afig1dpkIPSEUhg==",
+   "url": "ct.ws.symantec.com/",
+   "maximum_merge_delay": 86400,
+   "operated_by": [
+    2
+   ],
+   "dns_api_endpoint": "symantec.ct.googleapis.com"
+  },
+  {
+   "description": "Symantec 'Vega' log",
+   "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6pWeAv/u8TNtS4e8zf0ZF2L/lNPQWQc/Ai0ckP7IRzA78d0NuBEMXR2G3avTK0Zm+25ltzv9WWis36b4ztIYTQ==",
+   "url": "vega.ws.symantec.com/",
+   "maximum_merge_delay": 86400,
+   "operated_by": [
+    2
+   ],
+   "dns_api_endpoint": "symantec-vega.ct.googleapis.com"
+  },
+  {
+   "description": "Certly.IO log",
+   "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAECyPLhWKYYUgEc+tUXfPQB4wtGS2MNvXrjwFCCnyYJifBtd2Sk7Cu+Js9DNhMTh35FftHaHu6ZrclnNBKwmbbSA==",
+   "url": "log.certly.io/",
+   "maximum_merge_delay": 86400,
+   "operated_by": [
+    3
+   ],
+   "disqualified_at": 1460678400,
+   "dns_api_endpoint": "certly.ct.googleapis.com"
+  },
+  {
+   "description": "Izenpe log",
+   "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEJ2Q5DC3cUBj4IQCiDu0s6j51up+TZAkAEcQRF6tczw90rLWXkJMAW7jr9yc92bIKgV8vDXU4lDeZHvYHduDuvg==",
+   "url": "ct.izenpe.com/",
+   "maximum_merge_delay": 86400,
+   "operated_by": [
+    4
+   ],
+   "disqualified_at": 1464566400,
+   "dns_api_endpoint": "izenpe1.ct.googleapis.com"
+  },
+  {
+   "description": "WoSign log",
+   "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEzBGIey1my66PTTBmJxklIpMhRrQvAdPG+SvVyLpzmwai8IoCnNBrRhgwhbrpJIsO0VtwKAx+8TpFf1rzgkJgMQ==",
+   "url": "ctlog.wosign.com/",
+   "maximum_merge_delay": 86400,
+   "operated_by": [
+    5
+   ],
+   "dns_api_endpoint": "wosign1.ct.googleapis.com"
+  },
+  {
+   "description": "Venafi log",
+   "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAolpIHxdSlTXLo1s6H1OCdpSj/4DyHDc8wLG9wVmLqy1lk9fz4ATVmm+/1iN2Nk8jmctUKK2MFUtlWXZBSpym97M7frGlSaQXUWyA3CqQUEuIJOmlEjKTBEiQAvpfDjCHjlV2Be4qTM6jamkJbiWtgnYPhJL6ONaGTiSPm7Byy57iaz/hbckldSOIoRhYBiMzeNoA0DiRZ9KmfSeXZ1rB8y8X5urSW+iBzf2SaOfzBvDpcoTuAaWx2DPazoOl28fP1hZ+kHUYvxbcMjttjauCFx+JII0dmuZNIwjfeG/GBb9frpSX219k1O4Wi6OEbHEr8at/XQ0y7gTikOxBn/s5wQIDAQAB",
+   "url": "ctlog.api.venafi.com/",
+   "maximum_merge_delay": 86400,
+   "operated_by": [
+    6
+   ],
+   "disqualified_at": 1488307346,
+   "dns_api_endpoint": "venafi.ct.googleapis.com"
+  },
+  {
+   "description": "CNNIC CT log",
+   "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv7UIYZopMgTTJWPp2IXhhuAf1l6a9zM7gBvntj5fLaFm9pVKhKYhVnno94XuXeN8EsDgiSIJIj66FpUGvai5samyetZhLocRuXhAiXXbDNyQ4KR51tVebtEq2zT0mT9liTtGwiksFQccyUsaVPhsHq9gJ2IKZdWauVA2Fm5x9h8B9xKn/L/2IaMpkIYtd967TNTP/dLPgixN1PLCLaypvurDGSVDsuWabA3FHKWL9z8wr7kBkbdpEhLlg2H+NAC+9nGKx+tQkuhZ/hWR65aX+CNUPy2OB9/u2rNPyDydb988LENXoUcMkQT0dU3aiYGkFAY0uZjD2vH97TM20xYtNQIDAQAB",
+   "url": "ctserver.cnnic.cn/",
+   "maximum_merge_delay": 86400,
+   "operated_by": [
+    7
+   ],
+   "dns_api_endpoint": "cnnic.ct.googleapis.com"
+  },
+  {
+   "description": "StartCom log",
+   "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAESPNZ8/YFGNPbsu1Gfs/IEbVXsajWTOaft0oaFIZDqUiwy1o/PErK38SCFFWa+PeOQFXc9NKv6nV0+05/YIYuUQ==",
+   "url": "ct.startssl.com/",
+   "maximum_merge_delay": 86400,
+   "operated_by": [
+    8
+   ],
+   "dns_api_endpoint": "startcom1.ct.googleapis.com"
+  },
+  {
+   "description": "PuChuangSiDa CT log",
+   "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArM8vS3Cs8Q2Wv+gK/kSd1IwXncOaEBGEE+2M+Tdtg+QAb7FLwKaJx2GPmjS7VlLKA1ZQ7yR/S0npNYHd8OcX9XLSI8XjE3/Xjng1j0nemASKY6+tojlwlYRoS5Ez/kzhMhfC8mG4Oo05f9WVgj5WGVBFb8sIMw3VGUIIGkhCEPFow8NBE8sNHtsCtyR6UZZuvAjqaa9t75KYjlXzZeXonL4aR2AwfXqArVaDepPDrpMraiiKpl9jGQy+fHshY0E4t/fodnNrhcy8civBUtBbXTFOnSrzTZtkFJkmxnH4e/hE1eMjIPMK14tRPnKA0nh4NS1K50CZEZU01C9/+V81NwIDAQAB",
+   "url": "www.certificatetransparency.cn/ct/",
+   "maximum_merge_delay": 86400,
+   "operated_by": [
+    9
+   ],
+   "dns_api_endpoint": "puchuangsida1.ct.googleapis.com"
+  }
+ ],
+ "operators": [
+  {
+   "name": "Google",
+   "id": 0
+  },
+  {
+   "name": "DigiCert",
+   "id": 1
+  },
+  {
+   "name": "Symantec",
+   "id": 2
+  },
+  {
+   "name": "Certly",
+   "id": 3
+  },
+  {
+   "name": "Izenpe",
+   "id": 4
+  },
+  {
+   "name": "Wosign",
+   "id": 5
+  },
+  {
+   "name": "Venafi",
+   "id": 6
+  },
+  {
+   "name": "CNNIC",
+   "id": 7
+  },
+  {
+   "name": "StartSSL",
+   "id": 8
+  },
+  {
+   "name": "Beijing PuChuangSiDa Technology Ltd.",
+   "id": 9
+  }
+ ]
+}
\ No newline at end of file
diff --git a/net/tools/ct_log_list/PRESUBMIT.py b/net/tools/ct_log_list/PRESUBMIT.py
new file mode 100644
index 0000000..ff9bf5c1
--- /dev/null
+++ b/net/tools/ct_log_list/PRESUBMIT.py
@@ -0,0 +1,34 @@
+# Copyright 2017 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.
+
+
+"""Chromium presubmit script for src/net/tools/ct_log_list."""
+
+
+def _RunMakeCTLogListTests(input_api, output_api):
+  """Runs make_ct_known_logs_list unittests if related files were modified."""
+  files = ('net/tools/ct_log_list/make_ct_known_logs_list.py',
+           'net/tools/ct_log_list/make_ct_known_logs_list_unittest.py',
+           'net/data/ssl/certificate_transparency/log_list.json')
+  if not any(f in input_api.LocalPaths() for f in files):
+    return []
+  test_path = input_api.os_path.join(input_api.PresubmitLocalPath(),
+                                     'make_ct_known_logs_list_unittest.py')
+  cmd_name = 'make_ct_known_logs_list_unittest'
+  cmd = [input_api.python_executable, test_path]
+  test_cmd = input_api.Command(
+    name=cmd_name,
+    cmd=cmd,
+    kwargs={},
+    message=output_api.PresubmitPromptWarning)
+  return input_api.RunTests([test_cmd])
+
+
+def CheckChangeOnUpload(input_api, output_api):
+  return _RunMakeCTLogListTests(input_api, output_api)
+
+
+def CheckChangeOnCommit(input_api, output_api):
+  return _RunMakeCTLogListTests(input_api, output_api)
+
diff --git a/net/tools/ct_log_list/make_ct_known_logs_list.py b/net/tools/ct_log_list/make_ct_known_logs_list.py
new file mode 100755
index 0000000..01017d7e
--- /dev/null
+++ b/net/tools/ct_log_list/make_ct_known_logs_list.py
@@ -0,0 +1,222 @@
+#!/usr/bin/env python
+# Copyright 2017 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.
+"""Generate a C++ file containing information on all accepted CT logs."""
+
+import base64
+import hashlib
+import json
+import math
+import sys
+
+
+def _write_cpp_header(f):
+    f.write("// This file is auto-generated, DO NOT EDIT.\n\n")
+
+
+def _write_log_info_struct_definition(f):
+    f.write(
+        "struct CTLogInfo {\n"
+        "  // The DER-encoded SubjectPublicKeyInfo for the log.\n"
+        "  const char* log_key;\n"
+        "  // The length, in bytes, of |log_key|.\n"
+        "  size_t log_key_length;\n"
+        "  // The user-friendly log name.\n"
+        "  // Note: This will not be translated.\n"
+        "  const char* log_name;\n"
+        "  // The HTTPS API endpoint for the log.\n"
+        "  // Note: Trailing slashes should be included.\n"
+        "  const char* log_url;\n"
+        "  // The DNS API endpoint for the log.\n"
+        "  // This is used as the parent domain for all queries about the "
+        "log.\n  // If empty, CT DNS queries are not supported for the log. "
+        "This will prevent\n  // retrieval of inclusion proofs over DNS for "
+        "SCTs from the log.\n"
+        "  // https://github.com/google/certificate-transparency-rfcs/blob/"
+        "master/dns/draft-ct-over-dns.md.\n"
+        "  const char* log_dns_domain;\n"
+        "};\n\n"
+        )
+
+
+def _write_disqualified_log_info_struct_definition(f):
+    f.write(
+        "// Information related to previously-qualified, but now disqualified,"
+        "\n"
+        "// CT logs.\n"
+        "struct DisqualifiedCTLogInfo {\n"
+        "  // The ID of the log (the SHA-256 hash of |log_info.log_key|.\n"
+        "  const char log_id[33];\n"
+        "  const CTLogInfo log_info;\n"
+        "  // The offset from the Unix Epoch of when the log was disqualified."
+        "\n"
+        "  // SCTs embedded in pre-certificates after this date should not"
+        " count\n"
+        "  // towards any uniqueness/freshness requirements.\n"
+        "  const base::TimeDelta disqualification_date;\n"
+        "};\n\n")
+
+
+def _split_and_hexify_binary_data(bin_data):
+    """Pretty-prints, in hex, the given bin_data."""
+    hex_data = "".join(["\\x%.2x" % ord(c) for c in bin_data])
+    # line_width % 4 must be 0 to avoid splitting the hex-encoded data
+    # across '\' which will escape the quotation marks.
+    line_width = 68
+    assert line_width % 4 == 0
+    num_splits = int(math.ceil(len(hex_data) / float(line_width)))
+    return ['"%s"' % hex_data[i * line_width:(i + 1) * line_width]
+            for i in range(num_splits)]
+
+
+def _get_log_ids_array(log_ids, array_name):
+    num_logs = len(log_ids)
+    log_ids.sort()
+    log_id_length = len(log_ids[0]) + 1
+    log_id_code = [
+            "// The list is sorted.\n",
+            "const char %s[][%d] = {\n" % (array_name, log_id_length)]
+    for i in range(num_logs):
+        split_hex_id = _split_and_hexify_binary_data(log_ids[i])
+        s = "    %s" % ("\n    ".join(split_hex_id))
+        if (i < num_logs - 1):
+            s += ',\n'
+        log_id_code.append(s)
+    log_id_code.append('};\n\n')
+    return log_id_code
+
+
+
+def _find_google_operator_id(json_log_list):
+    goog_operator = [op for op in json_log_list["operators"]
+                     if op["name"] == "Google"]
+    if len(goog_operator) != 1:
+        raise RuntimeError("Google operator ID not found.")
+
+    return goog_operator[0]["id"]
+
+
+def _get_log_ids_for_operator(logs, operator_id):
+    """Returns a list of Log IDs of logs operated by operator_id."""
+    log_ids = []
+    for log in logs:
+        # operated_by is a list, in practice we have not witnessed
+        # a log co-operated by more than one operator. Ensure we take this
+        # case into consideration if it ever happens.
+        assert(len(log["operated_by"]) == 1)
+        if operator_id == log["operated_by"][0]:
+            log_key = base64.decodestring(log["key"])
+            log_ids.append(hashlib.sha256(log_key).digest())
+    return log_ids
+
+
+def _is_log_disqualified(log):
+    return log.get("disqualified_at") != None
+
+
+def _escape_c_string(s):
+    def _escape_char(c):
+        if 32 <= ord(c) <= 126 and c not in '\\"':
+            return c
+        else:
+            return '\\%03o' % ord(c)
+    return ''.join([_escape_char(c) for c in s])
+
+
+def _to_loginfo_struct(log):
+    """Converts the given log to a CTLogInfo initialization code."""
+    log_key = base64.decodestring(log["key"])
+    split_hex_key = _split_and_hexify_binary_data(log_key)
+    s = "    {"
+    s += "\n     ".join(split_hex_key)
+    s += ',\n     %d' % (len(log_key))
+    s += ',\n     "%s"' % (_escape_c_string(log["description"]))
+    s += ',\n     "https://%s"' % (log["url"])
+    s += ',\n     "%s"' % (log["dns_api_endpoint"])
+    s += '}'
+    return s
+
+
+def _get_log_definitions(logs):
+    """Returns a list of strings, each is a CTLogInfo definition."""
+    list_code = []
+    for log in logs:
+        list_code.append(_to_loginfo_struct(log))
+    return list_code
+
+
+def _to_disqualified_loginfo_struct(log):
+    log_key = base64.decodestring(log["key"])
+    log_id = hashlib.sha256(log_key).digest()
+    s = "    {"
+    s += "\n     ".join(_split_and_hexify_binary_data(log_id))
+    s += ",\n"
+    s += _to_loginfo_struct(log)
+    s += ",\n"
+    s += '     base::TimeDelta::FromSeconds(%d)' % (log["disqualified_at"])
+    s += '}'
+    return s
+
+
+def _get_disqualified_log_definitions(logs):
+    """Returns a list of DisqualifiedCTLogInfo definitions."""
+    list_code = []
+    for log in logs:
+        list_code.append(_to_disqualified_loginfo_struct(log))
+    return list_code
+
+
+def _sorted_disqualified_logs(all_logs):
+    return sorted(
+            filter(_is_log_disqualified, all_logs),
+            key=lambda l: hashlib.sha256(
+                base64.decodestring(l["key"])).digest())
+
+
+def _write_qualifying_logs_loginfo(f, qualifying_logs):
+    f.write("// The set of all presently-qualifying CT logs.\n"
+            "// Google provides DNS frontends for all of the logs.\n")
+    f.write("const CTLogInfo kCTLogList[] = {\n")
+    f.write(",\n".join(_get_log_definitions(qualifying_logs)))
+    f.write("\n};\n\n")
+
+
+def generate_cpp_file(input_file, f):
+    """Generate a header file of known logs to be included by Chromium."""
+    json_log_list = json.load(input_file)
+    _write_cpp_header(f)
+
+    logs = json_log_list["logs"]
+
+    # Write the list of currently-qualifying logs.
+    qualifying_logs = [log for log in logs if not _is_log_disqualified(log)]
+    _write_log_info_struct_definition(f)
+    _write_qualifying_logs_loginfo(f, qualifying_logs)
+
+    # Write the IDs of all CT Logs operated by Google
+    google_log_ids = _get_log_ids_for_operator(
+        logs, _find_google_operator_id(json_log_list))
+    f.writelines(_get_log_ids_array(google_log_ids, 'kGoogleLogIDs'))
+
+    # Write the list of all disqualified logs.
+    _write_disqualified_log_info_struct_definition(f)
+    f.write("// The set of all disqualified logs, sorted by |log_id|.\n")
+    f.write("const DisqualifiedCTLogInfo kDisqualifiedCTLogList[] = {\n")
+    f.write(",\n".join(
+            _get_disqualified_log_definitions(
+                    _sorted_disqualified_logs(logs))))
+    f.write("\n};\n")
+
+
+def main():
+  if len(sys.argv) != 3:
+    print('usage: %s in_loglist_json out_header' % sys.argv[0])
+    return 1
+  with open(sys.argv[1], 'r') as infile, open(sys.argv[2], 'w') as outfile:
+    generate_cpp_file(infile, outfile)
+  return 0
+
+
+if __name__ == '__main__':
+  sys.exit(main())
diff --git a/net/tools/ct_log_list/make_ct_known_logs_list_unittest.py b/net/tools/ct_log_list/make_ct_known_logs_list_unittest.py
new file mode 100755
index 0000000..804b3c2
--- /dev/null
+++ b/net/tools/ct_log_list/make_ct_known_logs_list_unittest.py
@@ -0,0 +1,155 @@
+#!/usr/bin/env python
+# Copyright 2017 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 base64
+import hashlib
+import sys
+import unittest
+import make_ct_known_logs_list
+
+
+def b64e(x):
+    return base64.encodestring(x)
+
+
+class FormattingTest(unittest.TestCase):
+    def testSplitAndHexifyBinData(self):
+        bin_data = ''.join([chr(i) for i in range(32,60)])
+        expected_encoded_array = [
+                ('"\\x20\\x21\\x22\\x23\\x24\\x25\\x26\\x27\\x28\\x29\\x2a'
+                 '\\x2b\\x2c\\x2d\\x2e\\x2f\\x30"'),
+                '"\\x31\\x32\\x33\\x34\\x35\\x36\\x37\\x38\\x39\\x3a\\x3b"']
+        self.assertEqual(
+                make_ct_known_logs_list._split_and_hexify_binary_data(
+                        bin_data),
+                expected_encoded_array)
+
+        # This data should fit in exactly one line - 17 bytes.
+        short_bin_data = ''.join([chr(i) for i in range(32, 49)])
+        expected_short_array = [
+                ('"\\x20\\x21\\x22\\x23\\x24\\x25\\x26\\x27\\x28\\x29\\x2a'
+                 '\\x2b\\x2c\\x2d\\x2e\\x2f\\x30"')]
+        self.assertEqual(
+                make_ct_known_logs_list._split_and_hexify_binary_data(
+                        short_bin_data),
+                expected_short_array)
+
+        # This data should fit exactly in two lines - 34 bytes.
+        two_line_data = ''.join([chr(i) for i in range(32, 66)])
+        expected_two_line_data_array = [
+                ('"\\x20\\x21\\x22\\x23\\x24\\x25\\x26\\x27\\x28\\x29\\x2a'
+                 '\\x2b\\x2c\\x2d\\x2e\\x2f\\x30"'),
+                ('"\\x31\\x32\\x33\\x34\\x35\\x36\\x37\\x38\\x39\\x3a\\x3b'
+                 '\\x3c\\x3d\\x3e\\x3f\\x40\\x41"')]
+        self.assertEqual(
+                make_ct_known_logs_list._split_and_hexify_binary_data(
+                        short_bin_data),
+                expected_short_array)
+
+    def testGetLogIDsArray(self):
+        log_ids = ["def", "abc", "ghi"]
+        expected_log_ids_code = [
+                "// The list is sorted.\n",
+                "const char kTestIDs[][4] = {\n",
+                '    "\\x61\\x62\\x63",\n',
+                '    "\\x64\\x65\\x66",\n',
+                '    "\\x67\\x68\\x69"',
+                "};\n\n"]
+        self.assertEqual(
+                make_ct_known_logs_list._get_log_ids_array(
+                        log_ids, "kTestIDs"),
+                expected_log_ids_code)
+
+    def testToLogInfoStruct(self):
+        log = {"key": "YWJj",
+               "description": "Test Description",
+               "url": "ct.example.com",
+               "dns_api_endpoint": "dns.ct.example.com"}
+        expected_loginfo = (
+                '    {"\\x61\\x62\\x63",\n     3,\n     "Test Description",\n'
+                '     "https://ct.example.com",\n     "dns.ct.example.com"}')
+        self.assertEqual(
+                make_ct_known_logs_list._to_loginfo_struct(log),
+                expected_loginfo)
+
+
+class OperatorIDHandlingTest(unittest.TestCase):
+    def testFindingGoogleOperatorID(self):
+        ops_list = {"operators": [
+                {"id": 0, "name": "First"},
+                {"id": 1, "name": "Second"}]}
+        self.assertRaises(
+                RuntimeError,
+                make_ct_known_logs_list._find_google_operator_id,
+                ops_list)
+        ops_list["operators"].append({"id": 2, "name": "Google"})
+        self.assertEqual(
+                make_ct_known_logs_list._find_google_operator_id(ops_list),
+                2)
+        ops_list["operators"].append({"id": 3, "name": "Google"})
+        self.assertRaises(
+                RuntimeError,
+                make_ct_known_logs_list._find_google_operator_id,
+                ops_list)
+
+    def testCollectingLogIDsByOperator(self):
+        logs = [
+                {"operated_by": (1,), "key": b64e('a')},
+                {"operated_by": (2,), "key": b64e('b')},
+                {"operated_by": (3,), "key": b64e('c')},
+                {"operated_by": (1,), "key": b64e('d')}
+        ]
+        log_ids = make_ct_known_logs_list._get_log_ids_for_operator(logs, 1)
+        self.assertEqual(2, len(log_ids))
+        self.assertItemsEqual(
+                [hashlib.sha256(t).digest() for t in ('a', 'd')],
+                log_ids)
+
+
+class DisqualifiedLogsHandlingTest(unittest.TestCase):
+    def testCorrectlyIdentifiesDisqualifiedLog(self):
+        self.assertTrue(
+                make_ct_known_logs_list._is_log_disqualified(
+                        {"disqualified_at" : 12345}))
+        self.assertFalse(
+                make_ct_known_logs_list._is_log_disqualified(
+                        {"name" : "example"}))
+
+    def testTranslatingToDisqualifiedLogDefinition(self):
+        log = {"key": "YWJj",
+               "description": "Test Description",
+               "url": "ct.example.com",
+               "dns_api_endpoint": "dns.ct.example.com",
+               "disqualified_at": 1464566400}
+        expected_disqualified_log_info = (
+            '    {"\\xba\\x78\\x16\\xbf\\x8f\\x01\\xcf\\xea\\x41\\x41\\x40'
+            '\\xde\\x5d\\xae\\x22\\x23\\xb0"\n     "\\x03\\x61\\xa3\\x96\\x17'
+            '\\x7a\\x9c\\xb4\\x10\\xff\\x61\\xf2\\x00\\x15\\xad",\n    {"\\x61'
+            '\\x62\\x63",\n     3,\n     "Test Description",\n     '
+            '"https://ct.example.com",\n     "dns.ct.example.com"},\n     '
+            'base::TimeDelta::FromSeconds(1464566400)}')
+
+        self.assertEqual(
+            make_ct_known_logs_list._to_disqualified_loginfo_struct(log),
+            expected_disqualified_log_info)
+
+    def testSortingAndFilteringDisqualifiedLogs(self):
+        logs = [
+                {"disqualified_at": 1, "key": b64e('a')},
+                {"key": b64e('b')},
+                {"disqualified_at": 3, "key": b64e('c')},
+                {"disqualified_at": 2, "key": b64e('d')},
+                {"key": b64e('e')}
+        ]
+        disqualified_logs = make_ct_known_logs_list._sorted_disqualified_logs(
+                logs)
+        self.assertEqual(3, len(disqualified_logs))
+        self.assertEqual(b64e('d'), disqualified_logs[0]["key"])
+        self.assertEqual(b64e('c'), disqualified_logs[1]["key"])
+        self.assertEqual(b64e('a'), disqualified_logs[2]["key"])
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/net/url_request/url_fetcher_core.cc b/net/url_request/url_fetcher_core.cc
index a4727c7c..a320f485 100644
--- a/net/url_request/url_fetcher_core.cc
+++ b/net/url_request/url_fetcher_core.cc
@@ -5,9 +5,12 @@
 #include "net/url_request/url_fetcher_core.h"
 
 #include <stdint.h>
+#include <algorithm>
 #include <utility>
 
 #include "base/bind.h"
+#include "base/debug/alias.h"
+#include "base/debug/dump_without_crashing.h"
 #include "base/logging.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/profiler/scoped_tracker.h"
@@ -106,13 +109,24 @@
       current_upload_bytes_(-1),
       current_response_bytes_(0),
       total_response_bytes_(-1),
-      traffic_annotation_(traffic_annotation) {
+      traffic_annotation_(traffic_annotation),
+      stack_identifier_(nullptr) {
   CHECK(original_url_.is_valid());
 }
 
 void URLFetcherCore::Start() {
   DCHECK(delegate_task_runner_);
   DCHECK(request_context_getter_.get()) << "We need an URLRequestContext!";
+
+  size_t stack_size = 0u;
+  stack_trace_on_start_ = base::MakeUnique<base::debug::StackTrace>();
+  const void* const* addresses = stack_trace_on_start_->Addresses(&stack_size);
+  // The #5 frame is the frame of the consumer. #0 and #1 are the constructor
+  // for StackTrace(). #2 is for MakeUnique. #3 is for URLFetcherCore::Start().
+  // #4 is URLFetcherImpl::Start().
+  if (stack_size > 5 && addresses)
+    stack_identifier_ = addresses[5];
+
   if (network_task_runner_.get()) {
     DCHECK_EQ(network_task_runner_,
               request_context_getter_->GetNetworkTaskRunner());
@@ -556,6 +570,12 @@
   request_context_getter_->AddObserver(this);
   request_ = request_context_getter_->GetURLRequestContext()->CreateRequest(
       original_url_, DEFAULT_PRIORITY, this, traffic_annotation_);
+
+  // TODO(xunjieli): Temporary to investigate crbug.com/711721.
+  if (!request_context_getter_->GetURLRequestContext()->AddToAddressMap(
+          stack_identifier_)) {
+    DumpWithoutCrashing();
+  }
   int flags = request_->load_flags() | load_flags_;
 
   // TODO(mmenke): This should really be with the other code to set the upload
@@ -820,6 +840,9 @@
 void URLFetcherCore::ReleaseRequest() {
   request_context_getter_->RemoveObserver(this);
   upload_progress_checker_timer_.reset();
+  if (request_)
+    request_->context()->RemoveFromAddressMap(stack_identifier_);
+
   request_.reset();
   buffer_ = nullptr;
   g_registry.Get().RemoveURLFetcherCore(this);
@@ -964,4 +987,24 @@
   DCHECK(upload_stream_factory_.is_null());
 }
 
+void URLFetcherCore::DumpWithoutCrashing() const {
+  DCHECK(stack_trace_on_start_);
+
+  size_t stack_size = 0u;
+  const void* const* instruction_pointers =
+      stack_trace_on_start_->Addresses(&stack_size);
+  static constexpr size_t kMaxStackSize = 100;
+  const void* instruction_pointers_copy[kMaxStackSize + 2];
+  // Insert markers bracketing the crash to make it easier to locate.
+  memset(&instruction_pointers_copy[0], 0xAB,
+         sizeof(instruction_pointers_copy[0]));
+  memset(instruction_pointers_copy, 0xAB, sizeof(instruction_pointers_copy));
+  stack_size = std::min(kMaxStackSize, stack_size);
+  std::memcpy(&instruction_pointers_copy[1], instruction_pointers,
+              stack_size * sizeof(const void*));
+  base::debug::Alias(&stack_size);
+  base::debug::Alias(&instruction_pointers_copy);
+  base::debug::DumpWithoutCrashing();
+}
+
 }  // namespace net
diff --git a/net/url_request/url_fetcher_core.h b/net/url_request/url_fetcher_core.h
index 53bcf62..503bbb40 100644
--- a/net/url_request/url_fetcher_core.h
+++ b/net/url_request/url_fetcher_core.h
@@ -12,6 +12,7 @@
 #include <string>
 
 #include "base/compiler_specific.h"
+#include "base/debug/stack_trace.h"
 #include "base/files/file_path.h"
 #include "base/lazy_instance.h"
 #include "base/macros.h"
@@ -230,6 +231,10 @@
   // Check if any upload data is set or not.
   void AssertHasNoUploadData() const;
 
+  // Calls base::debug::DumpWithoutCrashing().
+  // TODO(xunjieli): Temporary to investigate crbug.com/711721.
+  void DumpWithoutCrashing() const;
+
   URLFetcher* fetcher_;              // Corresponding fetcher object
   GURL original_url_;                // The URL we were asked to fetch
   GURL url_;                         // The URL we eventually wound up at
@@ -350,6 +355,10 @@
 
   const net::NetworkTrafficAnnotationTag traffic_annotation_;
 
+  // TODO(xunjieli): Temporary to investigate crbug.com/711721.
+  std::unique_ptr<base::debug::StackTrace> stack_trace_on_start_;
+  void const* stack_identifier_;
+
   static base::LazyInstance<Registry>::DestructorAtExit g_registry;
 
   DISALLOW_COPY_AND_ASSIGN(URLFetcherCore);
diff --git a/net/url_request/url_request_context.cc b/net/url_request/url_request_context.cc
index c7e4477..b1f2988f 100644
--- a/net/url_request/url_request_context.cc
+++ b/net/url_request/url_request_context.cc
@@ -5,6 +5,7 @@
 #include "net/url_request/url_request_context.h"
 
 #include <inttypes.h>
+#include <utility>
 
 #include "base/compiler_specific.h"
 #include "base/debug/alias.h"
@@ -52,7 +53,8 @@
       enable_brotli_(false),
       check_cleartext_permitted_(false),
       name_(nullptr),
-      largest_outstanding_requests_count_seen_(0) {
+      largest_outstanding_requests_count_seen_(0),
+      has_reported_too_many_outstanding_requests_(false) {
   base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(
       this, "URLRequestContext", base::ThreadTaskRunnerHandle::Get());
 }
@@ -123,6 +125,23 @@
   cookie_store_ = cookie_store;
 }
 
+bool URLRequestContext::AddToAddressMap(const void* const address) {
+  int count = ++address_map_[address];
+  if (!has_reported_too_many_outstanding_requests_ && count > 1000) {
+    has_reported_too_many_outstanding_requests_ = true;
+    return false;
+  }
+  return true;
+}
+
+void URLRequestContext::RemoveFromAddressMap(const void* const address) const {
+  auto iter = address_map_.find(address);
+  DCHECK(address_map_.end() != iter);
+  iter->second -= 1;
+  if (iter->second == 0)
+    address_map_.erase(iter);
+}
+
 void URLRequestContext::InsertURLRequest(const URLRequest* request) const {
   url_requests_.insert(request);
   if (url_requests_.size() > largest_outstanding_requests_count_seen_) {
@@ -152,6 +171,7 @@
     CHECK(false) << "Leaked " << num_requests << " URLRequest(s). First URL: "
                  << request->url().spec().c_str() << ".";
   }
+  DCHECK(address_map_.empty());
 }
 
 bool URLRequestContext::OnMemoryDump(
diff --git a/net/url_request/url_request_context.h b/net/url_request/url_request_context.h
index 206b0105..96025de 100644
--- a/net/url_request/url_request_context.h
+++ b/net/url_request/url_request_context.h
@@ -8,6 +8,7 @@
 #ifndef NET_URL_REQUEST_URL_REQUEST_CONTEXT_H_
 #define NET_URL_REQUEST_URL_REQUEST_CONTEXT_H_
 
+#include <map>
 #include <memory>
 #include <set>
 #include <string>
@@ -225,6 +226,15 @@
     return url_requests_;
   }
 
+  // TODO(xunjieli): Temporary to investigate crbug.com/711721.
+
+  // Adds |address| to |address_map_|. Return false if the same address has been
+  // added for more than 1000 times but is not yet removed from the map.
+  bool AddToAddressMap(const void* const address);
+
+  // Removes |address| from |address_map_|.
+  void RemoveFromAddressMap(const void* const address) const;
+
   void InsertURLRequest(const URLRequest* request) const;
 
   void RemoveURLRequest(const URLRequest* request) const;
@@ -333,6 +343,16 @@
   // |this| and are not yet destroyed. This doesn't need to be in CopyFrom.
   mutable size_t largest_outstanding_requests_count_seen_;
 
+  // TODO(xunjieli): Remove after crbug.com/711721 is fixed.
+
+  // A map of frame address to the number of outstanding requests that are
+  // associated with that address.
+  mutable std::map<const void* const, int> address_map_;
+
+  // Whether AddToAddressMap() has reported false. This is to avoid gathering
+  // too many crash dumps when users run into this scenario.
+  bool has_reported_too_many_outstanding_requests_;
+
   DISALLOW_COPY_AND_ASSIGN(URLRequestContext);
 };
 
diff --git a/net/url_request/url_request_context_unittest.cc b/net/url_request/url_request_context_unittest.cc
index 147e350..1597a39 100644
--- a/net/url_request/url_request_context_unittest.cc
+++ b/net/url_request/url_request_context_unittest.cc
@@ -5,16 +5,40 @@
 #include "net/url_request/url_request_context.h"
 
 #include <memory>
+#include <utility>
+#include <vector>
 
 #include "base/memory/ptr_util.h"
+#include "base/test/histogram_tester.h"
 #include "base/trace_event/memory_dump_request_args.h"
 #include "base/trace_event/process_memory_dump.h"
 #include "net/proxy/proxy_config_service_fixed.h"
+#include "net/test/url_request/url_request_failed_job.h"
+#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
 #include "net/url_request/url_request_context_builder.h"
+#include "net/url_request/url_request_filter.h"
+#include "net/url_request/url_request_interceptor.h"
+#include "net/url_request/url_request_test_util.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace net {
 
+namespace {
+
+class HangingRequestInterceptor : public URLRequestInterceptor {
+ public:
+  HangingRequestInterceptor() {}
+  ~HangingRequestInterceptor() override {}
+
+  URLRequestJob* MaybeInterceptRequest(
+      URLRequest* request,
+      NetworkDelegate* network_delegate) const override {
+    return new URLRequestFailedJob(request, network_delegate, ERR_IO_PENDING);
+  }
+};
+
+}  // namespace
+
 class URLRequestContextMemoryDumpTest
     : public testing::TestWithParam<
           base::trace_event::MemoryDumpLevelOfDetail> {};
@@ -66,4 +90,37 @@
 }
 
 // TODO(xunjieli): Add more granular tests on the MemoryDumpProvider.
+
+// Tests that if many requests are outstanding, histogram is reported correctly.
+TEST(URLRequestContextTest, TooManyRequests) {
+  TestURLRequestContext context(false);
+  base::HistogramTester histogram_tester;
+  std::unique_ptr<URLRequestInterceptor> interceptor(
+      new HangingRequestInterceptor());
+  GURL url("http://www.example.com");
+  URLRequestFilter::GetInstance()->AddUrlInterceptor(url,
+                                                     std::move(interceptor));
+  std::vector<std::unique_ptr<URLRequest>> outstanding_requests;
+  const int kNumRequestLimit = 1000;
+  // Make two more requests above the limit to test that AddToAddressMap() only
+  // returns false once.
+  const int kNumRequests = kNumRequestLimit + 2;
+  const void* const dummy_address = &context;
+  for (int i = 0; i < kNumRequests; ++i) {
+    TestDelegate test_delegate;
+    test_delegate.set_quit_on_complete(true);
+    std::unique_ptr<URLRequest> request = context.CreateRequest(
+        url, DEFAULT_PRIORITY, &test_delegate, TRAFFIC_ANNOTATION_FOR_TESTS);
+    EXPECT_EQ(i != kNumRequestLimit, context.AddToAddressMap(dummy_address));
+    request->Start();
+    outstanding_requests.push_back(std::move(request));
+  }
+
+  histogram_tester.ExpectTotalCount("Net.URLRequestContext.OutstandingRequests",
+                                    kNumRequests);
+  for (int i = 0; i < kNumRequests; ++i) {
+    context.RemoveFromAddressMap(dummy_address);
+  }
+}
+
 }  // namespace net
diff --git a/testing/buildbot/chromium.perf.fyi.json b/testing/buildbot/chromium.perf.fyi.json
index dc5c4935..d87b126 100644
--- a/testing/buildbot/chromium.perf.fyi.json
+++ b/testing/buildbot/chromium.perf.fyi.json
@@ -10964,65 +10964,6 @@
       },
       {
         "args": [
-          "spaceport",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "spaceport",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:22b1",
-              "id": "build155-b1",
-              "os": "Windows-10-10586",
-              "pool": "Chrome-perf-fyi"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "spaceport",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "spaceport.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:22b1",
-              "id": "build155-b1",
-              "os": "Windows-10-10586",
-              "pool": "Chrome-perf-fyi"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "speedometer",
           "-v",
           "--upload-results",
@@ -17507,65 +17448,6 @@
       },
       {
         "args": [
-          "spaceport",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "spaceport",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:9874",
-              "id": "build219-b4",
-              "os": "Windows-10-10586",
-              "pool": "Chrome-perf-fyi"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "spaceport",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "spaceport.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:9874",
-              "id": "build219-b4",
-              "os": "Windows-10-10586",
-              "pool": "Chrome-perf-fyi"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "speedometer",
           "-v",
           "--upload-results",
diff --git a/testing/buildbot/chromium.perf.json b/testing/buildbot/chromium.perf.json
index c03d7070..d4161ba 100644
--- a/testing/buildbot/chromium.perf.json
+++ b/testing/buildbot/chromium.perf.json
@@ -10951,65 +10951,6 @@
       },
       {
         "args": [
-          "spaceport",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "spaceport",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0534",
-              "id": "build149-m1",
-              "os": "Ubuntu-14.04",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "spaceport",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "spaceport.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0534",
-              "id": "build149-m1",
-              "os": "Ubuntu-14.04",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "speedometer",
           "-v",
           "--upload-results",
@@ -17514,65 +17455,6 @@
       },
       {
         "args": [
-          "spaceport",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "spaceport",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0166",
-              "id": "build103-b1",
-              "os": "Mac-10.11",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "spaceport",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "spaceport.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0166",
-              "id": "build103-b1",
-              "os": "Mac-10.11",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "speedometer",
           "-v",
           "--upload-results",
@@ -24037,65 +23919,6 @@
       },
       {
         "args": [
-          "spaceport",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "spaceport",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0a2e",
-              "id": "build159-m1",
-              "os": "Mac-10.12",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "spaceport",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "spaceport.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0a2e",
-              "id": "build159-m1",
-              "os": "Mac-10.12",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "speedometer",
           "-v",
           "--upload-results",
@@ -30560,65 +30383,6 @@
       },
       {
         "args": [
-          "spaceport",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "spaceport",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:1626",
-              "id": "build124-b1",
-              "os": "Mac-10.11",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "spaceport",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "spaceport.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:1626",
-              "id": "build124-b1",
-              "os": "Mac-10.11",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "speedometer",
           "-v",
           "--upload-results",
@@ -37083,65 +36847,6 @@
       },
       {
         "args": [
-          "spaceport",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "spaceport",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0a26",
-              "id": "build25-b1",
-              "os": "Mac-10.12",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "spaceport",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "spaceport.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0a26",
-              "id": "build25-b1",
-              "os": "Mac-10.12",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "speedometer",
           "-v",
           "--upload-results",
@@ -43606,65 +43311,6 @@
       },
       {
         "args": [
-          "spaceport",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "spaceport",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:6821",
-              "id": "build129-b1",
-              "os": "Mac-10.11",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "spaceport",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "spaceport.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:6821",
-              "id": "build129-b1",
-              "os": "Mac-10.11",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "speedometer",
           "-v",
           "--upload-results",
@@ -50129,65 +49775,6 @@
       },
       {
         "args": [
-          "spaceport",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "spaceport",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0d26",
-              "id": "build5-b1",
-              "os": "Mac-10.11",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "spaceport",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "spaceport.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:0d26",
-              "id": "build5-b1",
-              "os": "Mac-10.11",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "speedometer",
           "-v",
           "--upload-results",
@@ -56652,65 +56239,6 @@
       },
       {
         "args": [
-          "spaceport",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "spaceport",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:1616",
-              "id": "build118-b1",
-              "os": "Windows-10-10240",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "spaceport",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "spaceport.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:1616",
-              "id": "build118-b1",
-              "os": "Windows-10-10240",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "speedometer",
           "-v",
           "--upload-results",
@@ -63195,65 +62723,6 @@
       },
       {
         "args": [
-          "spaceport",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "spaceport",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0534",
-              "id": "build133-m1",
-              "os": "Windows-10-10240",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "spaceport",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "spaceport.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0534",
-              "id": "build133-m1",
-              "os": "Windows-10-10240",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "speedometer",
           "-v",
           "--upload-results",
@@ -69798,65 +69267,6 @@
       },
       {
         "args": [
-          "spaceport",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "spaceport",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:6613",
-              "id": "build102-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "spaceport",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "spaceport.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "1002:6613",
-              "id": "build102-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "speedometer",
           "-v",
           "--upload-results",
@@ -76381,65 +75791,6 @@
       },
       {
         "args": [
-          "spaceport",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "spaceport",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:041a",
-              "id": "build165-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "spaceport",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "spaceport.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:041a",
-              "id": "build165-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "speedometer",
           "-v",
           "--upload-results",
@@ -82984,65 +82335,6 @@
       },
       {
         "args": [
-          "spaceport",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "spaceport",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "10de:104a",
-              "id": "build93-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "spaceport",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "spaceport.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "10de:104a",
-              "id": "build93-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "speedometer",
           "-v",
           "--upload-results",
@@ -89567,65 +88859,6 @@
       },
       {
         "args": [
-          "spaceport",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "spaceport",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0532",
-              "id": "build186-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "spaceport",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "spaceport.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0532",
-              "id": "build186-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "speedometer",
           "-v",
           "--upload-results",
@@ -96130,65 +95363,6 @@
       },
       {
         "args": [
-          "spaceport",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "spaceport",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0532",
-              "id": "build139-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "spaceport",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "spaceport.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0532",
-              "id": "build139-m1",
-              "os": "Windows-2008ServerR2-SP1",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "speedometer",
           "-v",
           "--upload-results",
@@ -102713,65 +101887,6 @@
       },
       {
         "args": [
-          "spaceport",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "spaceport",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0532",
-              "id": "build144-m1",
-              "os": "Windows-2012ServerR2-SP0",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "spaceport",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "spaceport.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "102b:0532",
-              "id": "build144-m1",
-              "os": "Windows-2012ServerR2-SP0",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "speedometer",
           "-v",
           "--upload-results",
@@ -109236,65 +108351,6 @@
       },
       {
         "args": [
-          "spaceport",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=release_x64"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "spaceport",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:161e",
-              "id": "build31-b1",
-              "os": "Windows-10-10240",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": false,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
-          "spaceport",
-          "-v",
-          "--upload-results",
-          "--output-format=chartjson",
-          "--browser=reference",
-          "--output-trace-tag=_ref"
-        ],
-        "isolate_name": "telemetry_perf_tests",
-        "name": "spaceport.reference",
-        "override_compile_targets": [
-          "telemetry_perf_tests"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:161e",
-              "id": "build31-b1",
-              "os": "Windows-10-10240",
-              "pool": "Chrome-perf"
-            }
-          ],
-          "expiration": 36000,
-          "hard_timeout": 7200,
-          "ignore_task_failure": true,
-          "io_timeout": 3600
-        }
-      },
-      {
-        "args": [
           "speedometer",
           "-v",
           "--upload-results",
diff --git a/third_party/WebKit/LayoutTests/fast/dom/Window/window-open-invalid-url.html b/third_party/WebKit/LayoutTests/fast/dom/Window/window-open-invalid-url.html
new file mode 100644
index 0000000..2b03ed6
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/dom/Window/window-open-invalid-url.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<title>window.open: invalid URL</title>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script>
+  test(() => {
+      assert_throws("SyntaxError", () => { self.open("http:"); });
+  }, "Check that window.open() throws on an invalid URL.");
+</script>
diff --git a/third_party/WebKit/LayoutTests/http/tests/payments/payment-instruments.html b/third_party/WebKit/LayoutTests/http/tests/payments/payment-instruments.html
index 8b2c06bf..1e01ca6b 100644
--- a/third_party/WebKit/LayoutTests/http/tests/payments/payment-instruments.html
+++ b/third_party/WebKit/LayoutTests/http/tests/payments/payment-instruments.html
@@ -161,4 +161,54 @@
       .catch(unreached_rejection(test));
   }, 'PaymentInstruments |keys| method test');
 
+promise_test(test => {
+    var registration;
+    var script_url = 'resources/empty-worker.js';
+    var scope = 'resources/';
+
+    return service_worker_unregister_and_register(test, script_url, scope)
+      .then(r => {
+          registration = r;
+          return wait_for_state(test, registration.installing, 'activated');
+        })
+      .then(state => {
+          assert_equals(state, 'activated');
+          var instruments = [
+            registration.paymentManager.instruments.set(
+              'test_key1',
+              {
+                name: 'Visa ending ****4756',
+                enabledMethods: ['basic-card'],
+                capabilities: {
+                  supportedNetworks: ['visa'],
+                  supportedTypes: ['credit']
+                }
+              }),
+            registration.paymentManager.instruments.set(
+              'test_key2',
+              {
+                name: "My Bob Pay Account: john@example.com",
+                enabledMethods: ["https://bobpay.com/"]
+              })
+          ];
+          return Promise.all(instruments);
+        })
+      .then(result => {
+          assert_array_equals(result, [undefined, undefined]);
+          return registration.paymentManager.instruments.keys();
+        })
+      .then(result => {
+          assert_equals(result.length, 2);
+          return registration.paymentManager.instruments.clear();
+        })
+      .then(result => {
+          assert_equals(result, undefined);
+          return registration.paymentManager.instruments.keys();
+        })
+      .then(result => {
+          assert_equals(result.length, 0);
+        })
+      .catch(unreached_rejection(test));
+  }, 'PaymentInstruments |clear| method test');
+
 </script>
diff --git a/third_party/WebKit/LayoutTests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt b/third_party/WebKit/LayoutTests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
index 58596a0..6af047d 100644
--- a/third_party/WebKit/LayoutTests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
+++ b/third_party/WebKit/LayoutTests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
@@ -760,6 +760,7 @@
     method quadraticCurveTo
     method rect
 interface PaymentInstruments
+    method clear
     method constructor
     method delete
     method get
diff --git a/third_party/WebKit/LayoutTests/virtual/service-worker-navigation-preload-disabled/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt b/third_party/WebKit/LayoutTests/virtual/service-worker-navigation-preload-disabled/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
index ff02c2e..918d5d1 100644
--- a/third_party/WebKit/LayoutTests/virtual/service-worker-navigation-preload-disabled/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
+++ b/third_party/WebKit/LayoutTests/virtual/service-worker-navigation-preload-disabled/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
@@ -753,6 +753,7 @@
     method quadraticCurveTo
     method rect
 interface PaymentInstruments
+    method clear
     method constructor
     method delete
     method get
diff --git a/third_party/WebKit/LayoutTests/virtual/service-worker-navigation-preload/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt b/third_party/WebKit/LayoutTests/virtual/service-worker-navigation-preload/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
index 58596a0..6af047d 100644
--- a/third_party/WebKit/LayoutTests/virtual/service-worker-navigation-preload/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
+++ b/third_party/WebKit/LayoutTests/virtual/service-worker-navigation-preload/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
@@ -760,6 +760,7 @@
     method quadraticCurveTo
     method rect
 interface PaymentInstruments
+    method clear
     method constructor
     method delete
     method get
diff --git a/third_party/WebKit/Source/bindings/core/v8/custom/V8WindowCustom.cpp b/third_party/WebKit/Source/bindings/core/v8/custom/V8WindowCustom.cpp
index 62343eb2..a5ebd0fb 100644
--- a/third_party/WebKit/Source/bindings/core/v8/custom/V8WindowCustom.cpp
+++ b/third_party/WebKit/Source/bindings/core/v8/custom/V8WindowCustom.cpp
@@ -287,7 +287,11 @@
   // passed the BindingSecurity check above.
   DOMWindow* opened_window = ToLocalDOMWindow(impl)->open(
       url_string, frame_name, window_features_string,
-      CurrentDOMWindow(info.GetIsolate()), EnteredDOMWindow(info.GetIsolate()));
+      CurrentDOMWindow(info.GetIsolate()), EnteredDOMWindow(info.GetIsolate()),
+      exception_state);
+  if (exception_state.HadException()) {
+    return;
+  }
   if (!opened_window) {
     V8SetReturnValueNull(info);
     return;
diff --git a/third_party/WebKit/Source/core/frame/LocalDOMWindow.cpp b/third_party/WebKit/Source/core/frame/LocalDOMWindow.cpp
index 61cf04db..80de18a 100644
--- a/third_party/WebKit/Source/core/frame/LocalDOMWindow.cpp
+++ b/third_party/WebKit/Source/core/frame/LocalDOMWindow.cpp
@@ -1610,7 +1610,8 @@
                                 const AtomicString& frame_name,
                                 const String& window_features_string,
                                 LocalDOMWindow* calling_window,
-                                LocalDOMWindow* entered_window) {
+                                LocalDOMWindow* entered_window,
+                                ExceptionState& exception_state) {
   if (!IsCurrentlyDisplayedInFrame())
     return nullptr;
   if (!calling_window->GetFrame())
@@ -1668,7 +1669,7 @@
   WindowFeatures features(window_features_string);
   DOMWindow* new_window =
       CreateWindow(url_string, frame_name, features, *calling_window,
-                   *first_frame, *GetFrame());
+                   *first_frame, *GetFrame(), exception_state);
   return features.noopener ? nullptr : new_window;
 }
 
diff --git a/third_party/WebKit/Source/core/frame/LocalDOMWindow.h b/third_party/WebKit/Source/core/frame/LocalDOMWindow.h
index c4d4275..4f779245 100644
--- a/third_party/WebKit/Source/core/frame/LocalDOMWindow.h
+++ b/third_party/WebKit/Source/core/frame/LocalDOMWindow.h
@@ -54,6 +54,7 @@
 class DOMWindowEventQueue;
 class Element;
 class EventQueue;
+class ExceptionState;
 class External;
 class FrameConsole;
 class FrameRequestCallback;
@@ -272,7 +273,8 @@
                   const AtomicString& frame_name,
                   const String& window_features_string,
                   LocalDOMWindow* calling_window,
-                  LocalDOMWindow* entered_window);
+                  LocalDOMWindow* entered_window,
+                  ExceptionState&);
 
   FrameConsole* GetFrameConsole() const;
 
diff --git a/third_party/WebKit/Source/core/frame/Window.idl b/third_party/WebKit/Source/core/frame/Window.idl
index 2bbfaa86..015c9b4 100644
--- a/third_party/WebKit/Source/core/frame/Window.idl
+++ b/third_party/WebKit/Source/core/frame/Window.idl
@@ -63,7 +63,7 @@
     [Replaceable, CrossOrigin] readonly attribute Window? parent;
     [CheckSecurity=ReturnValue, Custom=Getter] readonly attribute Element? frameElement;
     // FIXME: open() should have 4 optional arguments with defaults.
-    [Custom] Window? open(DOMString url, DOMString target, optional DOMString features);
+    [Custom, RaisesException] Window? open(DOMString url, DOMString target, optional DOMString features);
 
     // indexed properties
     // https://html.spec.whatwg.org/C/browsers.html#windowproxy-getownproperty
diff --git a/third_party/WebKit/Source/core/page/CreateWindow.cpp b/third_party/WebKit/Source/core/page/CreateWindow.cpp
index afa0cff..9f51a0f 100644
--- a/third_party/WebKit/Source/core/page/CreateWindow.cpp
+++ b/third_party/WebKit/Source/core/page/CreateWindow.cpp
@@ -26,6 +26,7 @@
 
 #include "core/page/CreateWindow.h"
 
+#include "bindings/core/v8/ExceptionState.h"
 #include "core/dom/Document.h"
 #include "core/frame/FrameClient.h"
 #include "core/frame/LocalFrame.h"
@@ -179,7 +180,8 @@
                         const WindowFeatures& window_features,
                         LocalDOMWindow& calling_window,
                         LocalFrame& first_frame,
-                        LocalFrame& opener_frame) {
+                        LocalFrame& opener_frame,
+                        ExceptionState& exception_state) {
   LocalFrame* active_frame = calling_window.GetFrame();
   ASSERT(active_frame);
 
@@ -188,10 +190,9 @@
                            : first_frame.GetDocument()->CompleteURL(url_string);
   if (!completed_url.IsEmpty() && !completed_url.IsValid()) {
     UseCounter::Count(active_frame, UseCounter::kWindowOpenWithInvalidURL);
-    // Don't expose client code to invalid URLs.
-    calling_window.PrintErrorMessage(
-        "Unable to open a window with invalid URL '" +
-        completed_url.GetString() + "'.\n");
+    exception_state.ThrowDOMException(
+        kSyntaxError, "Unable to open a window with invalid URL '" +
+                          completed_url.GetString() + "'.\n");
     return nullptr;
   }
 
diff --git a/third_party/WebKit/Source/core/page/CreateWindow.h b/third_party/WebKit/Source/core/page/CreateWindow.h
index 2a69d23..ee4d3cc 100644
--- a/third_party/WebKit/Source/core/page/CreateWindow.h
+++ b/third_party/WebKit/Source/core/page/CreateWindow.h
@@ -36,6 +36,7 @@
 #undef CreateWindow
 
 namespace blink {
+class ExceptionState;
 class LocalFrame;
 struct FrameLoadRequest;
 struct WindowFeatures;
@@ -45,7 +46,8 @@
                         const WindowFeatures&,
                         LocalDOMWindow& calling_window,
                         LocalFrame& first_frame,
-                        LocalFrame& opener_frame);
+                        LocalFrame& opener_frame,
+                        ExceptionState&);
 
 void CreateWindowForRequest(const FrameLoadRequest&,
                             LocalFrame& opener_frame,
diff --git a/third_party/WebKit/Source/modules/payments/PaymentInstruments.cpp b/third_party/WebKit/Source/modules/payments/PaymentInstruments.cpp
index 746145f..8905048b 100644
--- a/third_party/WebKit/Source/modules/payments/PaymentInstruments.cpp
+++ b/third_party/WebKit/Source/modules/payments/PaymentInstruments.cpp
@@ -165,6 +165,22 @@
   return promise;
 }
 
+ScriptPromise PaymentInstruments::clear(ScriptState* script_state) {
+  if (!manager_.is_bound()) {
+    return ScriptPromise::RejectWithDOMException(
+        script_state,
+        DOMException::Create(kInvalidStateError, kPaymentManagerUnavailable));
+  }
+
+  ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state);
+  ScriptPromise promise = resolver->Promise();
+
+  manager_->ClearPaymentInstruments(ConvertToBaseCallback(
+      WTF::Bind(&PaymentInstruments::onClearPaymentInstruments,
+                WrapPersistent(this), WrapPersistent(resolver))));
+  return promise;
+}
+
 DEFINE_TRACE(PaymentInstruments) {}
 
 void PaymentInstruments::onDeletePaymentInstrument(
@@ -235,4 +251,13 @@
   resolver->Resolve();
 }
 
+void PaymentInstruments::onClearPaymentInstruments(
+    ScriptPromiseResolver* resolver,
+    payments::mojom::blink::PaymentHandlerStatus status) {
+  DCHECK(resolver);
+  if (rejectError(resolver, status))
+    return;
+  resolver->Resolve();
+}
+
 }  // namespace blink
diff --git a/third_party/WebKit/Source/modules/payments/PaymentInstruments.h b/third_party/WebKit/Source/modules/payments/PaymentInstruments.h
index 689c541..993d474 100644
--- a/third_party/WebKit/Source/modules/payments/PaymentInstruments.h
+++ b/third_party/WebKit/Source/modules/payments/PaymentInstruments.h
@@ -37,6 +37,7 @@
                     const String& instrument_key,
                     const PaymentInstrument& details,
                     ExceptionState&);
+  ScriptPromise clear(ScriptState*);
 
   DECLARE_TRACE();
 
@@ -53,6 +54,8 @@
                               payments::mojom::blink::PaymentHandlerStatus);
   void onSetPaymentInstrument(ScriptPromiseResolver*,
                               payments::mojom::blink::PaymentHandlerStatus);
+  void onClearPaymentInstruments(ScriptPromiseResolver*,
+                                 payments::mojom::blink::PaymentHandlerStatus);
 
   const payments::mojom::blink::PaymentManagerPtr& manager_;
 };
diff --git a/third_party/WebKit/Source/modules/payments/PaymentInstruments.idl b/third_party/WebKit/Source/modules/payments/PaymentInstruments.idl
index 2079ab6..1586755 100644
--- a/third_party/WebKit/Source/modules/payments/PaymentInstruments.idl
+++ b/third_party/WebKit/Source/modules/payments/PaymentInstruments.idl
@@ -13,4 +13,5 @@
     [CallWith=ScriptState] Promise<sequence<DOMString>> keys();
     [CallWith=ScriptState] Promise<boolean> has(DOMString instrumentKey);
     [CallWith=ScriptState, RaisesException] Promise<void> set(DOMString instrumentKey, PaymentInstrument details);
+    [CallWith=ScriptState] Promise<void> clear();
 };
diff --git a/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl.cc b/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl.cc
index f3369ecb..1dd4c106 100644
--- a/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl.cc
+++ b/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl.cc
@@ -470,6 +470,27 @@
   }
 }
 
+void RendererSchedulerImpl::BeginMainFrameNotExpectedUntil(
+    base::TimeTicks time) {
+  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
+               "RendererSchedulerImpl::BeginMainFrametime");
+  helper_.CheckOnValidThread();
+  if (helper_.IsShutdown())
+    return;
+
+  base::TimeTicks now(helper_.scheduler_tqm_delegate()->NowTicks());
+
+  if (now < time) {
+    // End any previous idle period.
+    EndIdlePeriod();
+
+    // TODO(rmcilroy): Consider reducing the idle period based on the runtime of
+    // the next pending delayed tasks (as currently done in for long idle times)
+    idle_helper_.StartIdlePeriod(
+        IdleHelper::IdlePeriodState::IN_SHORT_IDLE_PERIOD, now, time);
+  }
+}
+
 void RendererSchedulerImpl::SetAllRenderWidgetsHidden(bool hidden) {
   TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("renderer.scheduler"),
                "RendererSchedulerImpl::SetAllRenderWidgetsHidden", "hidden",
diff --git a/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl.h b/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl.h
index a9c0be3..01ac87f 100644
--- a/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl.h
+++ b/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl.h
@@ -90,6 +90,7 @@
       override;
   void WillBeginFrame(const cc::BeginFrameArgs& args) override;
   void BeginFrameNotExpectedSoon() override;
+  void BeginMainFrameNotExpectedUntil(base::TimeTicks time) override;
   void DidCommitFrameToCompositor() override;
   void DidHandleInputEventOnCompositorThread(
       const WebInputEvent& web_input_event,
diff --git a/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl_unittest.cc b/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl_unittest.cc
index f2f6239d..2cc3b00 100644
--- a/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl_unittest.cc
+++ b/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl_unittest.cc
@@ -2065,6 +2065,28 @@
                                           std::string("3")));
 }
 
+TEST_F(RendererSchedulerImplTest, TestBeginMainFrameNotExpectedUntil) {
+  base::TimeDelta ten_millis(base::TimeDelta::FromMilliseconds(10));
+  base::TimeTicks expected_deadline = clock_->NowTicks() + ten_millis;
+  base::TimeTicks deadline_in_task;
+  int run_count = 0;
+
+  idle_task_runner_->PostIdleTask(
+      FROM_HERE, base::Bind(&IdleTestTask, &run_count, &deadline_in_task));
+
+  RunUntilIdle();
+  EXPECT_EQ(0, run_count);  // Shouldn't run yet as no idle period.
+
+  base::TimeTicks now = clock_->NowTicks();
+  base::TimeTicks frame_time = now + ten_millis;
+  // No main frame is expected until frame_time, so short idle work can be
+  // scheduled in the mean time.
+  scheduler_->BeginMainFrameNotExpectedUntil(frame_time);
+  RunUntilIdle();
+  EXPECT_EQ(1, run_count);  // Should have run in a long idle time.
+  EXPECT_EQ(expected_deadline, deadline_in_task);
+}
+
 TEST_F(RendererSchedulerImplTest, TestLongIdlePeriod) {
   base::TimeTicks expected_deadline =
       clock_->NowTicks() + maximum_idle_period_duration();
diff --git a/third_party/WebKit/Source/platform/scheduler/test/fake_renderer_scheduler.cc b/third_party/WebKit/Source/platform/scheduler/test/fake_renderer_scheduler.cc
index c65d915..78f7640 100644
--- a/third_party/WebKit/Source/platform/scheduler/test/fake_renderer_scheduler.cc
+++ b/third_party/WebKit/Source/platform/scheduler/test/fake_renderer_scheduler.cc
@@ -51,6 +51,9 @@
 
 void FakeRendererScheduler::BeginFrameNotExpectedSoon() {}
 
+void FakeRendererScheduler::BeginMainFrameNotExpectedUntil(
+    base::TimeTicks time) {}
+
 void FakeRendererScheduler::DidCommitFrameToCompositor() {}
 
 void FakeRendererScheduler::DidHandleInputEventOnCompositorThread(
diff --git a/third_party/WebKit/Source/platform/testing/WebLayerTreeViewImplForTesting.h b/third_party/WebKit/Source/platform/testing/WebLayerTreeViewImplForTesting.h
index d543a05..b1a52888 100644
--- a/third_party/WebKit/Source/platform/testing/WebLayerTreeViewImplForTesting.h
+++ b/third_party/WebKit/Source/platform/testing/WebLayerTreeViewImplForTesting.h
@@ -78,6 +78,7 @@
   void DidBeginMainFrame() override {}
   void BeginMainFrame(const cc::BeginFrameArgs& args) override {}
   void BeginMainFrameNotExpectedSoon() override {}
+  void BeginMainFrameNotExpectedUntil(base::TimeTicks) override {}
   void UpdateLayerTreeHost() override;
   void ApplyViewportDeltas(const gfx::Vector2dF& inner_delta,
                            const gfx::Vector2dF& outer_delta,
diff --git a/third_party/WebKit/Source/web/tests/WebFrameTest.cpp b/third_party/WebKit/Source/web/tests/WebFrameTest.cpp
index 5235674..10f003dd 100644
--- a/third_party/WebKit/Source/web/tests/WebFrameTest.cpp
+++ b/third_party/WebKit/Source/web/tests/WebFrameTest.cpp
@@ -9450,15 +9450,16 @@
       ToWebLocalFrameImpl(MainFrame())->GetFrame()->DomWindow();
 
   KURL destination = ToKURL("data:text/html:destination");
+  NonThrowableExceptionState exception_state;
   main_window->open(destination.GetString(), "frame1", "", main_window,
-                    main_window);
+                    main_window, exception_state);
   ASSERT_FALSE(remote_client.LastRequest().IsNull());
   EXPECT_EQ(remote_client.LastRequest().Url(), WebURL(destination));
 
   // Pointing a named frame to an empty URL should just return a reference to
   // the frame's window without navigating it.
-  DOMWindow* result =
-      main_window->open("", "frame1", "", main_window, main_window);
+  DOMWindow* result = main_window->open("", "frame1", "", main_window,
+                                        main_window, exception_state);
   EXPECT_EQ(remote_client.LastRequest().Url(), WebURL(destination));
   EXPECT_EQ(result, WebFrame::ToCoreFrame(*remote_frame)->DomWindow());
 
diff --git a/third_party/WebKit/public/platform/scheduler/renderer/renderer_scheduler.h b/third_party/WebKit/public/platform/scheduler/renderer/renderer_scheduler.h
index 0f0a7d8..090f940 100644
--- a/third_party/WebKit/public/platform/scheduler/renderer/renderer_scheduler.h
+++ b/third_party/WebKit/public/platform/scheduler/renderer/renderer_scheduler.h
@@ -72,6 +72,12 @@
   // need to be drawn. Must be called from the main thread.
   virtual void BeginFrameNotExpectedSoon() = 0;
 
+  // Called to notify about the start of a period where main frames are not
+  // scheduled and so short idle work can be scheduled. This will precede
+  // BeginFrameNotExpectedSoon and is also called when the compositor may be
+  // busy but the main thread is not.
+  virtual void BeginMainFrameNotExpectedUntil(base::TimeTicks time) = 0;
+
   // Called to notify about the start of a new frame.  Must be called from the
   // main thread.
   virtual void WillBeginFrame(const cc::BeginFrameArgs& args) = 0;
diff --git a/third_party/WebKit/public/platform/scheduler/test/fake_renderer_scheduler.h b/third_party/WebKit/public/platform/scheduler/test/fake_renderer_scheduler.h
index 4bcc9d80..c828f19 100644
--- a/third_party/WebKit/public/platform/scheduler/test/fake_renderer_scheduler.h
+++ b/third_party/WebKit/public/platform/scheduler/test/fake_renderer_scheduler.h
@@ -27,6 +27,7 @@
       override;
   void WillBeginFrame(const cc::BeginFrameArgs& args) override;
   void BeginFrameNotExpectedSoon() override;
+  void BeginMainFrameNotExpectedUntil(base::TimeTicks time) override;
   void DidCommitFrameToCompositor() override;
   void DidHandleInputEventOnCompositorThread(
       const WebInputEvent& web_input_event,
diff --git a/third_party/WebKit/public/platform/scheduler/test/mock_renderer_scheduler.h b/third_party/WebKit/public/platform/scheduler/test/mock_renderer_scheduler.h
index 7cea5445..839747f0 100644
--- a/third_party/WebKit/public/platform/scheduler/test/mock_renderer_scheduler.h
+++ b/third_party/WebKit/public/platform/scheduler/test/mock_renderer_scheduler.h
@@ -33,6 +33,7 @@
                std::unique_ptr<RenderWidgetSchedulingState>());
   MOCK_METHOD1(WillBeginFrame, void(const cc::BeginFrameArgs&));
   MOCK_METHOD0(BeginFrameNotExpectedSoon, void());
+  MOCK_METHOD1(BeginMainFrameNotExpectedUntil, void(base::TimeTicks));
   MOCK_METHOD0(DidCommitFrameToCompositor, void());
   MOCK_METHOD2(DidHandleInputEventOnCompositorThread,
                void(const WebInputEvent&, InputEventState));
diff --git a/tools/gn/bootstrap/bootstrap.py b/tools/gn/bootstrap/bootstrap.py
index 5ba591a..399ccea 100755
--- a/tools/gn/bootstrap/bootstrap.py
+++ b/tools/gn/bootstrap/bootstrap.py
@@ -546,6 +546,7 @@
       'base/trace_event/memory_infra_background_whitelist.cc',
       'base/trace_event/memory_peak_detector.cc',
       'base/trace_event/memory_tracing_observer.cc',
+      'base/trace_event/memory_usage_estimator.cc',
       'base/trace_event/process_memory_dump.cc',
       'base/trace_event/process_memory_maps.cc',
       'base/trace_event/process_memory_totals.cc',
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index acef7c5..9327772 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -21849,6 +21849,7 @@
   <int value="1067618884" label="enable-experimental-input-view-features"/>
   <int value="1070164693" label="MidiManagerDynamicInstantiation:disabled"/>
   <int value="1070300488" label="disable-webgl"/>
+  <int value="1074359194" label="UseSuggestionsEvenIfFew:enabled"/>
   <int value="1081546525" label="ash-enable-docked-windows"/>
   <int value="1087235172" label="file-manager-enable-new-audio-player"/>
   <int value="1090377940" label="enable-quic-https"/>
@@ -22069,6 +22070,7 @@
   <int value="1865799183" label="javascript-harmony"/>
   <int value="1866079109" label="team-drives"/>
   <int value="1867085340" label="brotli-encoding:enabled"/>
+  <int value="1874604540" label="UseSuggestionsEvenIfFew:disabled"/>
   <int value="1878331098" label="GuestViewCrossProcessFrames:enabled"/>
   <int value="1881036528" label="disable-multilingual-spellchecker"/>
   <int value="1881174782" label="disable-brotli"/>
diff --git a/tools/perf/benchmark.csv b/tools/perf/benchmark.csv
index 1add5e47..d0797fc 100644
--- a/tools/perf/benchmark.csv
+++ b/tools/perf/benchmark.csv
@@ -121,7 +121,6 @@
 smoothness.tough_texture_upload_cases,vmiura@chromium.org,
 smoothness.tough_webgl_ad_cases,skyostil@chromium.org,
 smoothness.tough_webgl_cases,"kbr@chromium.org, zmo@chromium.org",
-spaceport,junov@chromium.org,
 speedometer,"bmeurer@chromium.org, mvstanton@chromium.org",
 speedometer-classic,hablich@chromium.org,
 speedometer-turbo,hablich@chromium.org,
diff --git a/tools/perf/benchmarks/benchmark_smoke_unittest.py b/tools/perf/benchmarks/benchmark_smoke_unittest.py
index b386869..b910e706 100644
--- a/tools/perf/benchmarks/benchmark_smoke_unittest.py
+++ b/tools/perf/benchmarks/benchmark_smoke_unittest.py
@@ -28,7 +28,6 @@
 from benchmarks import octane
 from benchmarks import rasterize_and_record_micro
 from benchmarks import repaint
-from benchmarks import spaceport
 from benchmarks import speedometer
 from benchmarks import text_selection
 from benchmarks import v8_browsing
@@ -96,7 +95,6 @@
     octane,  # Often fails & take long time to timeout on cq bot.
     rasterize_and_record_micro,  # Always fails on cq bot.
     repaint,  # Often fails & takes long time to timeout on cq bot.
-    spaceport,  # Takes 451 seconds.
     speedometer,  # Takes 101 seconds.
     jetstream,  # Take 206 seconds.
     text_selection,  # Always fails on cq bot.
diff --git a/tools/perf/benchmarks/spaceport.py b/tools/perf/benchmarks/spaceport.py
deleted file mode 100644
index cd80b078..0000000
--- a/tools/perf/benchmarks/spaceport.py
+++ /dev/null
@@ -1,128 +0,0 @@
-# Copyright 2012 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.
-
-"""Runs spaceport.io's PerfMarks benchmark."""
-
-import json
-import logging
-import os
-
-from core import path_util
-from core import perf_benchmark
-
-from telemetry import benchmark
-from telemetry import page as page_module
-from telemetry.page import legacy_page_test
-from telemetry import story
-from telemetry.value import list_of_scalar_values
-from telemetry.value import scalar
-
-DESCRIPTIONS = {
-    'canvasDrawImageFullClear':
-        'Using a canvas element to render. Bitmaps are blitted to the canvas '
-        'using the "drawImage" function and the canvas is fully cleared at '
-        'the beginning of each frame.',
-    'canvasDrawImageFullClearAlign':
-        'Same as canvasDrawImageFullClear except all "x" and "y" values are '
-        'rounded to the nearest integer. This can be more efficient on '
-        'translate on certain browsers.',
-    'canvasDrawImagePartialClear':
-        'Using a canvas element to render. Bitmaps are blitted to the canvas '
-        'using the "drawImage" function and pixels drawn in the last frame '
-        'are cleared to the clear color at the beginning of each frame. '
-        'This is generally slower on hardware accelerated implementations, '
-        'but sometimes faster on CPU-based implementations.',
-    'canvasDrawImagePartialClearAlign':
-        'Same as canvasDrawImageFullClearAlign but only partially clearing '
-        'the canvas each frame.',
-    'css2dBackground':
-        'Using div elements that have a background image specified using CSS '
-        'styles. These div elements are translated, scaled, and rotated using '
-        'CSS-2D transforms.',
-    'css2dImg':
-        'Same as css2dBackground, but using img elements instead of div '
-        'elements.',
-    'css3dBackground':
-        'Same as css2dBackground, but using CSS-3D transforms.',
-    'css3dImg':
-        'Same as css2dImage but using CSS-3D tranforms.',
-}
-
-
-class _SpaceportMeasurement(legacy_page_test.LegacyPageTest):
-
-  def __init__(self):
-    super(_SpaceportMeasurement, self).__init__()
-
-  def CustomizeBrowserOptions(self, options):
-    options.AppendExtraBrowserArgs('--disable-gpu-vsync')
-
-  def ValidateAndMeasurePage(self, page, tab, results):
-    del page  # unused
-    tab.WaitForJavaScriptCondition(
-        '!document.getElementById("start-performance-tests").disabled',
-        timeout=60)
-
-    tab.ExecuteJavaScript("""
-        window.__results = {};
-        window.console.log = function(str) {
-            if (!str) return;
-            var key_val = str.split(': ');
-            if (!key_val.length == 2) return;
-            __results[key_val[0]] = key_val[1];
-        };
-        document.getElementById('start-performance-tests').click();
-        """)
-
-    num_results = 0
-    num_tests_in_spaceport = 24
-    while num_results < num_tests_in_spaceport:
-      tab.WaitForJavaScriptCondition(
-          'Object.keys(window.__results).length > {{ num_results }}',
-          num_results=num_results,
-          timeout=180)
-      num_results = tab.EvaluateJavaScript(
-          'Object.keys(window.__results).length')
-      logging.info('Completed test %d of %d',
-                   num_results, num_tests_in_spaceport)
-
-    result_dict = json.loads(tab.EvaluateJavaScript(
-        'JSON.stringify(window.__results)'))
-    for key in result_dict:
-      chart, trace = key.split('.', 1)
-      results.AddValue(scalar.ScalarValue(
-          results.current_page, '%s.%s' % (chart, trace),
-          'objects (bigger is better)', float(result_dict[key]),
-          important=False, description=DESCRIPTIONS.get(chart)))
-    results.AddValue(list_of_scalar_values.ListOfScalarValues(
-        results.current_page, 'Score', 'objects (bigger is better)',
-        [float(x) for x in result_dict.values()],
-        description='Combined score for all parts of the spaceport benchmark.'))
-
-
-# crbug.com/166703: This test frequently times out on Windows.
-@benchmark.Disabled('mac', 'win',
-                    'linux', 'android')  # crbug.com/525112
-@benchmark.Owner(emails=['junov@chromium.org'])
-class Spaceport(perf_benchmark.PerfBenchmark):
-  """spaceport.io's PerfMarks benchmark.
-
-  http://spaceport.io/community/perfmarks
-
-  This test performs 3 animations (rotate, translate, scale) using a variety of
-  methods (css, webgl, canvas, etc) and reports the number of objects that can
-  be simultaneously animated while still achieving 30FPS.
-  """
-  test = _SpaceportMeasurement
-
-  @classmethod
-  def Name(cls):
-    return 'spaceport'
-
-  def CreateStorySet(self, options):
-    spaceport_dir = os.path.join(path_util.GetChromiumSrcDir(), 'chrome',
-                                 'test', 'data', 'third_party', 'spaceport')
-    ps = story.StorySet(base_dir=spaceport_dir)
-    ps.AddStory(page_module.Page('file://index.html', ps, ps.base_dir))
-    return ps
diff --git a/ui/compositor/compositor.cc b/ui/compositor/compositor.cc
index 45357f61..5557dd70 100644
--- a/ui/compositor/compositor.cc
+++ b/ui/compositor/compositor.cc
@@ -482,6 +482,8 @@
 void Compositor::BeginMainFrameNotExpectedSoon() {
 }
 
+void Compositor::BeginMainFrameNotExpectedUntil(base::TimeTicks time) {}
+
 static void SendDamagedRectsRecursive(ui::Layer* layer) {
   layer->SendDamagedRects();
   for (auto* child : layer->children())
diff --git a/ui/compositor/compositor.h b/ui/compositor/compositor.h
index 2e95a3f..ce43299 100644
--- a/ui/compositor/compositor.h
+++ b/ui/compositor/compositor.h
@@ -326,6 +326,7 @@
   void DidBeginMainFrame() override {}
   void BeginMainFrame(const cc::BeginFrameArgs& args) override;
   void BeginMainFrameNotExpectedSoon() override;
+  void BeginMainFrameNotExpectedUntil(base::TimeTicks time) override;
   void UpdateLayerTreeHost() override;
   void ApplyViewportDeltas(const gfx::Vector2dF& inner_delta,
                            const gfx::Vector2dF& outer_delta,