Safari extension: Event for a completely new tab?

2019-02-15 10:08发布

I'm looking to redirect new tabs in Safari to a certain URL, in essence changing the default new tab page. However, I do not want to redirect new tabs that are opened by following links.

The Windows and Tabs API describes tab "open" events, but these are fired when any new tab opens (including new tabs opened from links).

Is there a way to catch a new tab event for only those tabs that are created by clicking the new tab button?

Thanks in advance!

1条回答
The star\"
2楼-- · 2019-02-15 10:25

In your open event handler, first check if the event target is a tab. If it is, register an event handler for the beforeNavigate event on that tab. Also set a timeout to deregister the handler if the beforeNavigate event does not fire within, say, 50 milliseconds. For example:

function handleOpen(e) {
    if (e.target instanceof SafariBrowserTab) {
        e.target.addEventListener('beforeNavigate', handleBeforeNavigate, false);
        setTimeout(function () {
            e.target.removeEventListener('beforeNavigate', handleBeforeNavigate, false);
        }, 50);
    }
}

Now, there are three relevant possible outcomes for the beforeNavigate event following a tab open event.

Case 1: The beforeNavigate event will not fire within the timeout. This usually means that the tab is empty. An empty tab cannot be the result of a link click, so we can put some code in the timeout function to do whatever we like with it:

function handleOpen(e) {
    if (e.target instanceof SafariBrowserTab) {
        e.target.addEventListener('beforeNavigate', handleBeforeNavigate, false);
        setTimeout(function () {
            e.target.removeEventListener('beforeNavigate', handleBeforeNavigate, false);
            takeOverTab();
        }, 50);
    }
}

Case 2: The beforeNavigate event will fire, and its url property will have a value of null. This means that the tab will load either the Top Sites page or the Bookmarks page (two special Safari pages that are among the options for new tabs). Like an empty tab, such a tab cannot possibly be the result of a link click, so we can do what we like with the tab. For example:

function handleBeforeNavigate(e) {
    e.target.removeEventListener('beforeNavigate', handleBeforeNavigate, false);
    if (e.url === null) {
        takeOverTab();
    }
}

Case 3: The beforeNavigate will fire, and its url value will be a non-empty string: that is, an actual URL. This is the tricky case, since the tab could have resulted from either a link click, the new-tab command (if the user has configured Safari to open new tabs to his home page), or the action of another app or extension. Let's call the case where the tab was the result of a link click Case 3A; the case where it was the result of the new-tab command, Case 3B; and the other-app case, Case 3C.

I can't think of a way to distinguish between 3B and 3C. If there really isn't a way to distinguish these two cases, it could be very bad for your app, since you presumably don't want to redirect tabs that are opened by other apps.

If you don't care whether your app interferes with 3C tabs, there is at least one way to tell 3A tabs from 3B/3C tabs. This is the way that occurs to me:

  • Use an injected script in every page to listen for link clicks. When a link is clicked, relay the URL and the time of the click to the global page, which will remember them for future reference. For example:

    function handleMessage(e) {
        if (e.name === 'linkClicked') {
            lastClickedLinkUrl = e.message.url;
            lastLinkClickTime = e.message.clickTime;
        }
    }
    
  • In your beforeNavigate handler, test whether the event's url value is different from the URL of the last-clicked link. If it is, the new tab did not result from that link click, and thus it probably did not result from any link click. If the two URLs are the same, check whether more than, say, 100 milliseconds have elapsed since the last link click. If that's the case, it's probably just a coincidence that the URLs are the same, and so again you can infer that the new tab did not result from a link click. For example:

    function handleBeforeNavigate(e) {
        e.target.removeEventListener('beforeNavigate', handleBeforeNavigate, false);
        if (e.url === null) {
            takeOverTab();
        } else if (e.url !== lastClickedLinkUrl || (new Date) - lastLinkClickTime > 100) {
            takeOverTab();
        }
    }
    

Note that this detection method is not foolproof. There are probably a number of ways it could go wrong. Still, I hope this helps.

查看更多
登录 后发表回答